Spaces:
Paused
Paused
Download bot.py from mohammed978/whitewolf-cloud: direct link, hf CLI and curl.
- Browser
- Download file 30.4 kB
-
https://huggingface.co/spaces/mohammed978/whitewolf-cloud/resolve/main/bot.py
- Command line
-
hf download hf://spaces/mohammed978/whitewolf-cloud/bot.py
-
curl -L -o bot.py https://huggingface.co/spaces/mohammed978/whitewolf-cloud/resolve/main/bot.py
30.4 kB
| import telebot | |
| from telebot import types | |
| import sqlite3 | |
| import threading | |
| import os | |
| import sys | |
| import time | |
| import uuid | |
| import json | |
| import psutil | |
| import zipfile | |
| import shutil | |
| import ast | |
| import random | |
| import string | |
| import logging | |
| import subprocess | |
| import signal | |
| from datetime import datetime, timedelta | |
| from apscheduler.schedulers.background import BackgroundScheduler | |
| # ========================================== | |
| # ⚙️ الإعدادات الأساسية | |
| # ========================================== | |
| TOKEN = '8611700781:AAFWN-i1FZJUgHHfon7jZB6euH-ReRb_31E' | |
| ADMIN_ID = 6812997550 | |
| YOUR_USERNAME = '@EQJ_1' | |
| CHANNELS = ['@EQJ_1'] | |
| HOSTING_DIR = 'wolf_projects' | |
| BACKUP_DIR = 'wolf_backups' | |
| DB_NAME = 'whitewolf_cloud.db' | |
| VERSION = '1.1.3' | |
| for d in [HOSTING_DIR, BACKUP_DIR]: | |
| os.makedirs(d, exist_ok=True) | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger("WhiteWolf-Cloud") | |
| bot = telebot.TeleBot(TOKEN, parse_mode="Markdown") | |
| # ========================================== | |
| # 📊 الخطط والأسعار (مثل السابق) | |
| # ========================================== | |
| PLAN_LIMITS = { | |
| 'free': {'projects': 1, 'label': '🆓 مجاني', 'price': 0, 'ram': '50m'}, | |
| 'silver': {'projects': 3, 'label': '🥈 فضي', 'price': 10, 'ram': '100m'}, | |
| 'gold': {'projects': 5, 'label': '🥇 ذهبي', 'price': 25, 'ram': '250m'}, | |
| 'diamond': {'projects': 10, 'label': '💎 ماسي', 'price': 50, 'ram': '512m'}, | |
| } | |
| INVITE_REWARD = 2 | |
| # ========================================== | |
| # 🗄️ قاعدة البيانات (نفس الكود القديم) | |
| # ========================================== | |
| class DatabaseEngine: | |
| def __init__(self): | |
| self.lock = threading.Lock() | |
| self.init_db() | |
| def get_conn(self): | |
| conn = sqlite3.connect(DB_NAME, check_same_thread=False) | |
| conn.row_factory = sqlite3.Row | |
| return conn | |
| def init_db(self): | |
| with self.lock, self.get_conn() as conn: | |
| c = conn.cursor() | |
| c.execute('''CREATE TABLE IF NOT EXISTS users ( | |
| user_id INTEGER PRIMARY KEY, username TEXT, points INTEGER DEFAULT 0, | |
| plan TEXT DEFAULT 'free', plan_expires TIMESTAMP, is_vip INTEGER DEFAULT 0, | |
| total_invites INTEGER DEFAULT 0, is_banned INTEGER DEFAULT 0, joined_at TIMESTAMP | |
| )''') | |
| c.execute('''CREATE TABLE IF NOT EXISTS projects ( | |
| project_id TEXT PRIMARY KEY, user_id INTEGER, file_name TEXT, path TEXT, | |
| container_id TEXT, status TEXT DEFAULT 'pending', auto_restart INTEGER DEFAULT 1, | |
| env_vars TEXT DEFAULT '{}', created_at TIMESTAMP | |
| )''') | |
| c.execute('''CREATE TABLE IF NOT EXISTS coupons ( | |
| code TEXT PRIMARY KEY, value INTEGER, max_uses INTEGER, used_count INTEGER DEFAULT 0 | |
| )''') | |
| c.execute('''CREATE TABLE IF NOT EXISTS templates ( | |
| id TEXT PRIMARY KEY, name TEXT, desc TEXT, path TEXT, downloads INTEGER DEFAULT 0 | |
| )''') | |
| conn.commit() | |
| def add_user(self, user_id, username): | |
| with self.lock, self.get_conn() as conn: | |
| conn.execute('INSERT OR IGNORE INTO users (user_id, username, joined_at) VALUES (?, ?, ?)', | |
| (user_id, username, datetime.now())) | |
| conn.commit() | |
| def get_user(self, user_id): | |
| with self.get_conn() as conn: | |
| return conn.execute('SELECT * FROM users WHERE user_id = ?', (user_id,)).fetchone() | |
| def add_points(self, user_id, amount): | |
| with self.lock, self.get_conn() as conn: | |
| conn.execute('UPDATE users SET points = points + ? WHERE user_id = ?', (amount, user_id)) | |
| conn.commit() | |
| def deduct_points(self, user_id, amount): | |
| with self.lock, self.get_conn() as conn: | |
| conn.execute('UPDATE users SET points = points - ? WHERE user_id = ?', (amount, user_id)) | |
| conn.commit() | |
| def set_plan(self, user_id, plan, days=30): | |
| expires = datetime.now() + timedelta(days=days) | |
| with self.lock, self.get_conn() as conn: | |
| conn.execute('UPDATE users SET plan = ?, plan_expires = ? WHERE user_id = ?', (plan, expires, user_id)) | |
| conn.commit() | |
| def get_projects(self, user_id=None): | |
| with self.get_conn() as conn: | |
| if user_id: return conn.execute('SELECT * FROM projects WHERE user_id = ?', (user_id,)).fetchall() | |
| return conn.execute('SELECT * FROM projects').fetchall() | |
| def update_project_status(self, project_id, pid, status): | |
| with self.lock, self.get_conn() as conn: | |
| conn.execute('UPDATE projects SET container_id = ?, status = ? WHERE project_id = ?', | |
| (pid, status, project_id)) | |
| conn.commit() | |
| def delete_project(self, project_id): | |
| with self.lock, self.get_conn() as conn: | |
| conn.execute('DELETE FROM projects WHERE project_id = ?', (project_id,)) | |
| conn.commit() | |
| def update_env(self, project_id, env_vars): | |
| with self.lock, self.get_conn() as conn: | |
| conn.execute('UPDATE projects SET env_vars = ? WHERE project_id = ?', (env_vars, project_id)) | |
| conn.commit() | |
| db = DatabaseEngine() | |
| # ========================================== | |
| # 🛡️ فحص الأمان (نفس الكود) | |
| # ========================================== | |
| class SecurityScanner: | |
| def is_safe_code(file_path, user_id): | |
| if user_id == ADMIN_ID: return True, "Admin Bypass" | |
| try: | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| tree = ast.parse(f.read()) | |
| banned_imports = ['os', 'subprocess', 'pty', 'shutil', 'socket', 'sys'] | |
| banned_funcs = ['eval', 'exec', 'open', 'system', 'popen'] | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.Import): | |
| for alias in node.names: | |
| if alias.name in banned_imports: return False, f"مكتبة ممنوعة: {alias.name}" | |
| elif isinstance(node, ast.Call) and getattr(node.func, 'id', '') in banned_funcs: | |
| return False, f"دالة خطيرة: {node.func.id}" | |
| return True, "Safe" | |
| except Exception as e: | |
| return False, f"خطأ في الكود: {str(e)[:50]}" | |
| # ========================================== | |
| # 🐳 محرك **محلي** بديل عن Docker (يدير العمليات) | |
| # ========================================== | |
| class LocalProcessEngine: | |
| """يدير المشاريع كعمليات Python فرعية مع logs و stats""" | |
| processes = {} # project_id -> subprocess.Popen | |
| log_files = {} # project_id -> path to log file | |
| def deploy(project_id, file_path, ram_limit, env_vars_str, auto_restart): | |
| # ram_limit مثلاً '100m' - نحوله إلى بايت (نهمله حالياً) | |
| # نقرأ متغيرات البيئة | |
| env_dict = json.loads(env_vars_str) if env_vars_str else {} | |
| env = os.environ.copy() | |
| env.update(env_dict) | |
| log_path = os.path.join(HOSTING_DIR, f"{project_id}.log") | |
| log_file = open(log_path, 'a') | |
| # إيقاف العملية القديمة إن وجدت | |
| LocalProcessEngine.stop_and_remove(project_id) | |
| # تشغيل العملية | |
| try: | |
| proc = subprocess.Popen( | |
| [sys.executable, file_path], | |
| stdout=log_file, | |
| stderr=log_file, | |
| env=env, | |
| cwd=os.path.dirname(file_path), | |
| preexec_fn=os.setsid # لتسهيل قتل المجموعة | |
| ) | |
| LocalProcessEngine.processes[project_id] = proc | |
| LocalProcessEngine.log_files[project_id] = log_path | |
| return True, str(proc.pid) | |
| except Exception as e: | |
| log_file.close() | |
| return False, str(e) | |
| def stop_and_remove(project_id): | |
| if project_id in LocalProcessEngine.processes: | |
| proc = LocalProcessEngine.processes[project_id] | |
| try: | |
| # قتل العملية ومجموعتها | |
| os.killpg(os.getpgid(proc.pid), signal.SIGTERM) | |
| proc.terminate() | |
| proc.wait(timeout=5) | |
| except: | |
| pass | |
| finally: | |
| del LocalProcessEngine.processes[project_id] | |
| if project_id in LocalProcessEngine.log_files: | |
| # لا نحذف اللوج | |
| pass | |
| def get_logs(project_id): | |
| log_path = LocalProcessEngine.log_files.get(project_id) | |
| if not log_path or not os.path.exists(log_path): | |
| return "لا توجد سجلات بعد." | |
| try: | |
| with open(log_path, 'r', encoding='utf-8') as f: | |
| lines = f.readlines() | |
| return ''.join(lines[-25:]) # آخر 25 سطر | |
| except: | |
| return "خطأ في قراءة السجلات." | |
| def get_stats(project_id): | |
| proc = LocalProcessEngine.processes.get(project_id) | |
| if not proc or proc.poll() is not None: | |
| return "متوقف 🔴" | |
| try: | |
| p = psutil.Process(proc.pid) | |
| mem = p.memory_info().rss / (1024 * 1024) # MB | |
| cpu = p.cpu_percent(interval=0.1) | |
| return f"🟢 يعمل | RAM: {mem:.1f}MB | CPU: {cpu:.1f}%" | |
| except: | |
| return "يعمل (لا توجد إحصاءات)" | |
| # نستخدم المحرك المحلي بدل Docker | |
| ProcessEngine = LocalProcessEngine | |
| # ========================================== | |
| # 🎨 واجهة المستخدم (نفس الكود القديم) | |
| # ========================================== | |
| class UI: | |
| def main_menu(user_id): | |
| markup = types.InlineKeyboardMarkup(row_width=2) | |
| markup.add( | |
| types.InlineKeyboardButton("📤 رفع مشروع", callback_data="ui_upload"), | |
| types.InlineKeyboardButton("📋 إدارة مشاريعي", callback_data="ui_projects"), | |
| types.InlineKeyboardButton("👤 حسابي", callback_data="ui_profile"), | |
| types.InlineKeyboardButton("🛒 المتجر", callback_data="ui_store"), | |
| types.InlineKeyboardButton("🔗 كسب نقاط", callback_data="ui_earn"), | |
| types.InlineKeyboardButton("🎟️ استرداد كوبون", callback_data="ui_coupon"), | |
| types.InlineKeyboardButton("📚 القوالب الجاهزة", callback_data="ui_templates"), | |
| types.InlineKeyboardButton("👨💻 الدعم", url=f"https://t.me/{YOUR_USERNAME[1:]}") | |
| ) | |
| if user_id == ADMIN_ID: | |
| markup.add(types.InlineKeyboardButton("🛡️ لوحة المطور", callback_data="admin_root")) | |
| return markup | |
| def project_ctrl(project_id, status, auto_restart): | |
| markup = types.InlineKeyboardMarkup(row_width=2) | |
| if status == 'running': | |
| markup.add(types.InlineKeyboardButton("🛑 إيقاف", callback_data=f"p_stop_{project_id}")) | |
| else: | |
| markup.add(types.InlineKeyboardButton("▶️ تشغيل", callback_data=f"p_start_{project_id}")) | |
| markup.add( | |
| types.InlineKeyboardButton("📋 السجلات", callback_data=f"p_logs_{project_id}"), | |
| types.InlineKeyboardButton("🔄 ريستارت", callback_data=f"p_restart_{project_id}"), | |
| types.InlineKeyboardButton("🌍 ENV vars", callback_data=f"p_env_{project_id}"), | |
| types.InlineKeyboardButton("📊 الإحصائيات", callback_data=f"p_stats_{project_id}") | |
| ) | |
| ar_text = "🔁 إيقاف الريستارت التلقائي" if auto_restart else "🔁 تفعيل الريستارت التلقائي" | |
| markup.add( | |
| types.InlineKeyboardButton(ar_text, callback_data=f"p_ar_{project_id}"), | |
| types.InlineKeyboardButton("🗑️ حذف", callback_data=f"p_del_{project_id}") | |
| ) | |
| markup.add(types.InlineKeyboardButton("🔙 رجوع", callback_data="ui_projects")) | |
| return markup | |
| def back(callback_data="ui_main"): | |
| return types.InlineKeyboardMarkup().add(types.InlineKeyboardButton("🔙 رجوع", callback_data=callback_data)) | |
| # ========================================== | |
| # دوال المساعدة والاشتراك الإجباري | |
| # ========================================== | |
| user_states = {} | |
| def check_sub(user_id): | |
| if user_id == ADMIN_ID: return True | |
| user = db.get_user(user_id) | |
| if user and user['is_vip']: return True | |
| for ch in CHANNELS: | |
| try: | |
| status = bot.get_chat_member(ch, user_id).status | |
| if status not in ['member', 'administrator', 'creator']: return False | |
| except: return False | |
| return True | |
| def safe_edit(call, text, markup=None): | |
| try: | |
| bot.edit_message_text(text, call.message.chat.id, call.message.message_id, reply_markup=markup, parse_mode="Markdown") | |
| except: pass | |
| # ========================================== | |
| # أوامر البوت الأساسية | |
| # ========================================== | |
| def start_cmd(message): | |
| user_id = message.from_user.id | |
| username = message.from_user.username or f"User{user_id}" | |
| db.add_user(user_id, username) | |
| user = db.get_user(user_id) | |
| if user['is_banned']: | |
| return bot.reply_to(message, "🚫 حسابك محظور.") | |
| if not check_sub(user_id): | |
| markup = types.InlineKeyboardMarkup() | |
| for ch in CHANNELS: markup.add(types.InlineKeyboardButton("📡 اشترك هنا أولاً", url=f"https://t.me/{ch[1:]}")) | |
| markup.add(types.InlineKeyboardButton("✅ تم الاشتراك", callback_data="ui_main")) | |
| return bot.send_message(user_id, "⚠️ يجب الاشتراك في القناة:", reply_markup=markup) | |
| # نظام الإحالة | |
| args = message.text.split() | |
| if len(args) > 1 and args[1].isdigit(): | |
| inviter = int(args[1]) | |
| if inviter != user_id and not user['total_invites']: | |
| db.add_points(inviter, INVITE_REWARD) | |
| with db.get_conn() as c: | |
| c.execute('UPDATE users SET total_invites = total_invites + 1 WHERE user_id = ?', (inviter,)) | |
| c.execute('UPDATE users SET total_invites = -1 WHERE user_id = ?', (user_id,)) | |
| c.commit() | |
| bot.send_message(inviter, f"🎉 ربحت {INVITE_REWARD} نقطة لدعوتك مستخدم جديد!") | |
| plan_name = PLAN_LIMITS.get(user['plan'], PLAN_LIMITS['free'])['label'] | |
| text = f"🐺 **مرحباً في White Wolf Cloud**\n\nأقوى نظام استضافة سحابي (نسخة HuggingFace)\n📦 خطتك: {plan_name}" | |
| bot.send_message(user_id, text, reply_markup=UI.main_menu(user_id)) | |
| def ui_handler(call): | |
| user_id = call.from_user.id | |
| action = call.data[3:] | |
| user = db.get_user(user_id) | |
| if not check_sub(user_id): | |
| return bot.answer_callback_query(call.id, "❌ اشترك بالقناة أولاً", show_alert=True) | |
| if action == "main": | |
| plan_name = PLAN_LIMITS.get(user['plan'], PLAN_LIMITS['free'])['label'] | |
| safe_edit(call, f"🐺 **لوحة التحكم**\n📦 خطتك: {plan_name}", UI.main_menu(user_id)) | |
| elif action == "profile": | |
| projs = db.get_projects(user_id) | |
| plan_info = PLAN_LIMITS.get(user['plan'], PLAN_LIMITS['free']) | |
| text = f"👤 **الملف الشخصي**\n🆔 `{user_id}`\n💰 النقاط: `{user['points']}`\n📦 الخطة: {plan_info['label']}\n📂 المشاريع: `{len(projs)}/{plan_info['projects']}`\n🚀 الرام المسموح: `{plan_info['ram']}`" | |
| safe_edit(call, text, UI.back()) | |
| elif action == "earn": | |
| link = f"https://t.me/{bot.get_me().username}?start={user_id}" | |
| safe_edit(call, f"🔗 **رابط الدعوة:**\n`{link}`\n\n🎁 {INVITE_REWARD} نقطة لكل مدعو.", UI.back()) | |
| elif action == "store": | |
| markup = types.InlineKeyboardMarkup(row_width=1) | |
| for k, v in PLAN_LIMITS.items(): | |
| if k == 'free': continue | |
| markup.add(types.InlineKeyboardButton(f"{v['label']} | {v['projects']} مشاريع | {v['price']} نقطة", callback_data=f"buy_{k}")) | |
| markup.add(types.InlineKeyboardButton("🔙 رجوع", callback_data="ui_main")) | |
| safe_edit(call, f"🛒 **المتجر**\n💰 رصيدك: `{user['points']}` نقطة\nالمدة: 30 يوم.", markup) | |
| elif action == "projects": | |
| projs = db.get_projects(user_id) | |
| if not projs: return bot.answer_callback_query(call.id, "📭 لا توجد مشاريع.", show_alert=True) | |
| markup = types.InlineKeyboardMarkup() | |
| for p in projs: | |
| icon = "🟢" if p['status'] == 'running' else "🔴" | |
| markup.add(types.InlineKeyboardButton(f"{icon} {p['file_name']}", callback_data=f"p_view_{p['project_id']}")) | |
| markup.add(types.InlineKeyboardButton("🔙 رجوع", callback_data="ui_main")) | |
| safe_edit(call, "📋 **قائمة مشاريعك:**", markup) | |
| elif action == "upload": | |
| limit = PLAN_LIMITS.get(user['plan'], PLAN_LIMITS['free'])['projects'] | |
| if len(db.get_projects(user_id)) >= limit and not user['is_vip'] and user_id != ADMIN_ID: | |
| return bot.answer_callback_query(call.id, f"⚠️ وصلت للحد ({limit}). قم بالترقية!", show_alert=True) | |
| user_states[user_id] = 'upload' | |
| safe_edit(call, "📤 **أرسل ملف المشروع الآن** (.py أو .zip)\nأرسل /cancel للإلغاء", UI.back()) | |
| def buy_plan(call): | |
| plan_key = call.data[4:] | |
| user = db.get_user(call.from_user.id) | |
| price = PLAN_LIMITS[plan_key]['price'] | |
| if user['points'] < price: | |
| return bot.answer_callback_query(call.id, f"❌ تحتاج {price} نقطة.", show_alert=True) | |
| db.deduct_points(user['user_id'], price) | |
| db.set_plan(user['user_id'], plan_key) | |
| bot.answer_callback_query(call.id, "✅ تم تفعيل الخطة!", show_alert=True) | |
| ui_handler(types.CallbackQuery(call.id, call.from_user, call.data, call.chat_instance, call.message, data="ui_profile")) | |
| # ========================================== | |
| # إدارة المشاريع باستخدام المحرك المحلي | |
| # ========================================== | |
| def project_control(call): | |
| user_id = call.from_user.id | |
| parts = call.data.split('_') | |
| action = parts[1] | |
| project_id = parts[2] | |
| with db.get_conn() as conn: | |
| p = conn.execute('SELECT * FROM projects WHERE project_id = ?', (project_id,)).fetchone() | |
| if not p or (p['user_id'] != user_id and user_id != ADMIN_ID): | |
| return bot.answer_callback_query(call.id, "❌ مشروع غير موجود.", show_alert=True) | |
| if action == "view": | |
| stats = ProcessEngine.get_stats(project_id) if p['status'] == 'running' else "متوقف 🔴" | |
| text = f"📄 **معلومات المشروع:**\n📁 `{p['file_name']}`\n🆔 `{project_id}`\n📊 الحالة: {stats}" | |
| safe_edit(call, text, UI.project_ctrl(project_id, p['status'], p['auto_restart'])) | |
| elif action == "start": | |
| user = db.get_user(user_id) | |
| ram_limit = PLAN_LIMITS.get(user['plan'], PLAN_LIMITS['free'])['ram'] | |
| success, pid = ProcessEngine.deploy(project_id, p['path'], ram_limit, p['env_vars'], p['auto_restart']) | |
| if success: | |
| db.update_project_status(project_id, pid, 'running') | |
| bot.answer_callback_query(call.id, "▶️ تم التشغيل!") | |
| else: | |
| bot.answer_callback_query(call.id, f"❌ فشل: {pid}", show_alert=True) | |
| project_control(types.CallbackQuery(call.id, call.from_user, call.data, call.chat_instance, call.message, data=f"p_view_{project_id}")) | |
| elif action == "stop": | |
| ProcessEngine.stop_and_remove(project_id) | |
| db.update_project_status(project_id, None, 'stopped') | |
| bot.answer_callback_query(call.id, "🛑 تم الإيقاف.") | |
| project_control(types.CallbackQuery(call.id, call.from_user, call.data, call.chat_instance, call.message, data=f"p_view_{project_id}")) | |
| elif action == "restart": | |
| bot.answer_callback_query(call.id, "🔄 جاري إعادة التشغيل...") | |
| ProcessEngine.stop_and_remove(project_id) | |
| user = db.get_user(user_id) | |
| ram_limit = PLAN_LIMITS.get(user['plan'], PLAN_LIMITS['free'])['ram'] | |
| success, pid = ProcessEngine.deploy(project_id, p['path'], ram_limit, p['env_vars'], p['auto_restart']) | |
| if success: db.update_project_status(project_id, pid, 'running') | |
| project_control(types.CallbackQuery(call.id, call.from_user, call.data, call.chat_instance, call.message, data=f"p_view_{project_id}")) | |
| elif action == "logs": | |
| logs = ProcessEngine.get_logs(project_id) | |
| safe_edit(call, f"📋 **السجلات (آخر 25 سطر):**\n```\n{logs[-3500:]}\n```", UI.back(f"p_view_{project_id}")) | |
| elif action == "stats": | |
| stats = ProcessEngine.get_stats(project_id) | |
| safe_edit(call, f"📊 **الموارد الحية:**\n{stats}", UI.back(f"p_view_{project_id}")) | |
| elif action == "ar": | |
| new_val = 0 if p['auto_restart'] else 1 | |
| with db.lock, db.get_conn() as conn: | |
| conn.execute('UPDATE projects SET auto_restart = ? WHERE project_id = ?', (new_val, project_id)) | |
| conn.commit() | |
| bot.answer_callback_query(call.id, "✅ تم التعديل.") | |
| project_control(types.CallbackQuery(call.id, call.from_user, call.data, call.chat_instance, call.message, data=f"p_view_{project_id}")) | |
| elif action == "del": | |
| ProcessEngine.stop_and_remove(project_id) | |
| try: shutil.rmtree(os.path.dirname(p['path'])) if os.path.isdir(os.path.dirname(p['path'])) else os.remove(p['path']) | |
| except: pass | |
| db.delete_project(project_id) | |
| bot.answer_callback_query(call.id, "🗑️ تم الحذف.", show_alert=True) | |
| ui_handler(types.CallbackQuery(call.id, call.from_user, call.data, call.chat_instance, call.message, data="ui_projects")) | |
| elif action == "env": | |
| # عرض نافذة لتعديل env vars (يمكن إرسالها في رسالة جديدة) | |
| markup = types.InlineKeyboardMarkup() | |
| markup.add(types.InlineKeyboardButton("✏️ تعديل المتغيرات", callback_data=f"p_edit_env_{project_id}")) | |
| markup.add(types.InlineKeyboardButton("🔙 رجوع", callback_data=f"p_view_{project_id}")) | |
| safe_edit(call, f"🌍 **متغيرات البيئة الحالية:**\n```json\n{json.dumps(json.loads(p['env_vars'] or '{}'), indent=2)}\n```\nيمكنك تعديلها عبر إرسال JSON جديد.", markup) | |
| elif action == "edit_env": | |
| user_states[user_id] = f"env_{project_id}" | |
| bot.send_message(user_id, "أرسل المتغيرات الجديدة بصيغة JSON:\nمثال: {\"TOKEN\":\"123\", \"API\":\"abc\"}\nأو /cancel للإلغاء.") | |
| bot.answer_callback_query(call.id) | |
| def handle_env_edit(message): | |
| user_id = message.from_user.id | |
| state = user_states.pop(user_id, '') | |
| project_id = state.split('_')[1] | |
| try: | |
| new_vars = json.loads(message.text) | |
| if not isinstance(new_vars, dict): raise ValueError | |
| db.update_env(project_id, json.dumps(new_vars)) | |
| bot.reply_to(message, "✅ تم تحديث متغيرات البيئة. أعد تشغيل المشروع ليتم تطبيقها.") | |
| except: | |
| bot.reply_to(message, "❌ صيغة JSON غير صالحة. أرسل قيمة مثل: {\"key\":\"value\"}") | |
| # ========================================== | |
| # رفع الملفات (نفس الكود القديم مع تعديل بسيط) | |
| # ========================================== | |
| def handle_docs(message): | |
| user_id = message.from_user.id | |
| if user_states.get(user_id) != 'upload': return | |
| user_states.pop(user_id, None) | |
| file_name = message.document.file_name | |
| if not (file_name.endswith('.py') or file_name.endswith('.zip')): | |
| return bot.reply_to(message, "❌ مسموح فقط .py أو .zip") | |
| if message.document.file_size > 5 * 1024 * 1024: | |
| return bot.reply_to(message, "❌ الحد الأقصى 5MB") | |
| msg = bot.reply_to(message, "⏳ جاري الفحص...") | |
| project_id = str(uuid.uuid4())[:8] | |
| proj_dir = os.path.join(HOSTING_DIR, f"{user_id}_{project_id}") | |
| os.makedirs(proj_dir, exist_ok=True) | |
| save_path = os.path.join(proj_dir, file_name) | |
| downloaded = bot.download_file(bot.get_file(message.document.file_id).file_path) | |
| with open(save_path, 'wb') as f: f.write(downloaded) | |
| main_file = save_path | |
| if file_name.endswith('.zip'): | |
| try: | |
| with zipfile.ZipFile(save_path, 'r') as zf: | |
| for member in zf.namelist(): | |
| if not os.path.realpath(os.path.join(proj_dir, member)).startswith(os.path.realpath(proj_dir)): | |
| raise Exception("Zip Slip attack") | |
| zf.extractall(proj_dir) | |
| os.remove(save_path) | |
| py_files = [f for f in os.listdir(proj_dir) if f.endswith('.py')] | |
| if not py_files: raise Exception("لا يوجد ملف بايثون") | |
| main_file = os.path.join(proj_dir, 'main.py' if 'main.py' in py_files else py_files[0]) | |
| file_name = os.path.basename(main_file) | |
| except Exception as e: | |
| shutil.rmtree(proj_dir) | |
| return bot.edit_message_text(f"❌ خطأ: {e}", message.chat.id, msg.message_id) | |
| is_safe, reason = SecurityScanner.is_safe_code(main_file, user_id) | |
| if not is_safe: | |
| shutil.rmtree(proj_dir) | |
| return bot.edit_message_text(f"🚨 **مرفوض أمنياً:**\n`{reason}`", message.chat.id, msg.message_id) | |
| with db.lock, db.get_conn() as conn: | |
| conn.execute('INSERT INTO projects (project_id, user_id, file_name, path, created_at) VALUES (?, ?, ?, ?, ?)', | |
| (project_id, user_id, file_name, main_file, datetime.now())) | |
| conn.commit() | |
| bot.edit_message_text("✅ **تم حفظ المشروع!**\nاذهب إلى `مشاريعي` لتشغيله.", message.chat.id, msg.message_id, reply_markup=UI.main_menu(user_id)) | |
| def cancel_cmd(message): | |
| user_states.pop(message.from_user.id, None) | |
| bot.reply_to(message, "❌ تم الإلغاء.") | |
| # ========================================== | |
| # لوحة المطور (Admin Panel) مختصرة | |
| # ========================================== | |
| def admin_panel(call): | |
| if call.from_user.id != ADMIN_ID: return | |
| action = call.data[6:] | |
| if action == "root": | |
| markup = types.InlineKeyboardMarkup(row_width=2) | |
| markup.add( | |
| types.InlineKeyboardButton("📊 إحصائيات", callback_data="admin_stats"), | |
| types.InlineKeyboardButton("📢 إذاعة", callback_data="admin_bc"), | |
| types.InlineKeyboardButton("🎁 كوبون", callback_data="admin_add_coupon"), | |
| types.InlineKeyboardButton("🛑 إيقاف كل العمليات", callback_data="admin_killall") | |
| ) | |
| markup.add(types.InlineKeyboardButton("🔙 خروج", callback_data="ui_main")) | |
| safe_edit(call, "🛡️ **لوحة المطور**", markup) | |
| elif action == "stats": | |
| with db.get_conn() as conn: | |
| users = conn.execute('SELECT COUNT(*) FROM users').fetchone()[0] | |
| projs = conn.execute('SELECT COUNT(*) FROM projects').fetchone()[0] | |
| cpu = psutil.cpu_percent() | |
| ram = psutil.virtual_memory().percent | |
| disk = psutil.disk_usage('/').percent | |
| text = f"📊 **إحصائيات**\n👥 مستخدمين: `{users}`\n📂 مشاريع: `{projs}`\n🧠 CPU: `{cpu}%`\n💾 RAM: `{ram}%`\n💿 تخزين: `{disk}%`" | |
| safe_edit(call, text, UI.back("admin_root")) | |
| elif action == "killall": | |
| # إيقاف كل العمليات | |
| for pid, proc in list(ProcessEngine.processes.items()): | |
| try: os.killpg(os.getpgid(proc.pid), signal.SIGTERM) | |
| except: pass | |
| ProcessEngine.processes.clear() | |
| with db.lock, db.get_conn() as conn: | |
| conn.execute('UPDATE projects SET status = "stopped", container_id = NULL') | |
| conn.commit() | |
| bot.answer_callback_query(call.id, "⚡ تم إيقاف جميع المشاريع!", show_alert=True) | |
| admin_panel(types.CallbackQuery(call.id, call.from_user, call.data, call.chat_instance, call.message, data="admin_root")) | |
| # ========================================== | |
| # الصيانة الدورية (نفس الكود) | |
| # ========================================== | |
| def system_maintenance(): | |
| with db.lock, db.get_conn() as conn: | |
| expired = conn.execute("SELECT user_id FROM users WHERE plan != 'free' AND plan_expires < ?", (datetime.now(),)).fetchall() | |
| for u in expired: | |
| conn.execute("UPDATE users SET plan = 'free' WHERE user_id = ?", (u[0],)) | |
| try: bot.send_message(u[0], "⚠️ انتهت صلاحية خطتك المدفوعة.") | |
| except: pass | |
| conn.commit() | |
| backup_path = os.path.join(BACKUP_DIR, f"db_backup_{datetime.now().strftime('%A')}.db") | |
| shutil.copy2(DB_NAME, backup_path) | |
| logger.info("✅ تمت الصيانة الدورية.") | |
| scheduler = BackgroundScheduler() | |
| scheduler.add_job(system_maintenance, 'interval', hours=12) | |
| scheduler.start() | |
| # ========================================== | |
| # التشغيل | |
| # ========================================== | |
| if __name__ == '__main__': | |
| logger.info(f"🐺 إقلاع نظام White Wolf Cloud v{VERSION}...") | |
| try: | |
| bot.infinity_polling(timeout=60, long_polling_timeout=30) | |
| except Exception as e: | |
| logger.error(f"خطأ عام: {e}") |