diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -18,16 +18,14 @@ import base64 from functools import wraps from contextlib import contextmanager -# إنشاء المجلدات الضرورية قبل أي شيء +# إنشاء المجلدات الضرورية os.makedirs('logs', exist_ok=True) -os.makedirs('videos', exist_ok=True) +os.makedirs('temp_videos', exist_ok=True) os.makedirs('avatars', exist_ok=True) os.makedirs('thumbnails', exist_ok=True) -os.makedirs('encrypted', exist_ok=True) -os.makedirs('watermarked', exist_ok=True) os.makedirs('affiliate_logs', exist_ok=True) -# إعداد التسجيل بعد التأكد من وجود المجلد +# إعداد التسجيل logging.basicConfig( filename='logs/app.log', level=logging.INFO, @@ -35,28 +33,35 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) -# محاولة استيراد المكتبات مع fallback +# محاولة استيراد المكتبات try: from PIL import Image, ImageDraw, ImageFont HAS_PIL = True except ImportError: HAS_PIL = False - logger.warning("PIL غير مثبت، ميزات الصور محدودة.") + logger.warning("PIL غير مثبت") try: import cv2 HAS_CV2 = True except ImportError: HAS_CV2 = False - logger.warning("OpenCV غير مثبت، ميزات كشف NSFW وتحليل الفيديو غير متاحة.") + logger.warning("OpenCV غير مثبت") try: - from Crypto.Cipher import AES - from Crypto.Util.Padding import pad, unpad - HAS_CRYPTO = True + import cloudinary + import cloudinary.uploader + import cloudinary.api + CLOUDINARY_CONFIG = { + 'cloud_name': 'dpylnwrw0', + 'api_key': '631276857136451', + 'api_secret': 'xpehguQcV_7nj0iBXsMNM5PssHE' + } + cloudinary.config(**CLOUDINARY_CONFIG) + HAS_CLOUDINARY = True except ImportError: - HAS_CRYPTO = False - logger.warning("PyCryptodome غير مثبت، تشفير الفيديوهات لن يعمل.") + HAS_CLOUDINARY = False + logger.warning("Cloudinary غير مثبت") from flask import ( Flask, request, session, g, jsonify, render_template_string, @@ -65,19 +70,17 @@ from flask import ( from werkzeug.utils import secure_filename from werkzeug.security import generate_password_hash, check_password_hash -# ---------- التهيئة والإعدادات ---------- +# تهيئة التطبيق app = Flask(__name__) app.secret_key = secrets.token_hex(32) app.config['SESSION_TYPE'] = 'filesystem' app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=30) app.config['JSON_AS_ASCII'] = False -# إعدادات رفع الملفات -UPLOAD_FOLDER = os.path.join(os.getcwd(), 'videos') +# إعدادات الملفات +UPLOAD_FOLDER = os.path.join(os.getcwd(), 'temp_videos') AVATAR_FOLDER = os.path.join(os.getcwd(), 'avatars') THUMBNAIL_FOLDER = os.path.join(os.getcwd(), 'thumbnails') -ENCRYPTED_FOLDER = os.path.join(os.getcwd(), 'encrypted') -WATERMARK_FOLDER = os.path.join(os.getcwd(), 'watermarked') AFFILIATE_LOG_FOLDER = os.path.join(os.getcwd(), 'affiliate_logs') ALLOWED_VIDEO_EXTENSIONS = {'mp4', 'mov', 'avi', 'mkv', 'webm', '3gp'} ALLOWED_IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'} @@ -86,21 +89,15 @@ ALLOWED_PLUGIN_EXTENSIONS = {'py'} app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['AVATAR_FOLDER'] = AVATAR_FOLDER app.config['THUMBNAIL_FOLDER'] = THUMBNAIL_FOLDER -app.config['ENCRYPTED_FOLDER'] = ENCRYPTED_FOLDER -app.config['WATERMARK_FOLDER'] = WATERMARK_FOLDER app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024 -# مفتاح التشفير العام -ENCRYPTION_KEY = hashlib.sha256(app.secret_key.encode()).digest() - # قاعدة البيانات DATABASE = os.path.join(os.getcwd(), 'arc_video.db') VECTOR_DIM = 10 notification_queues = {} developer_plugins = {} -active_streams = {} -# ---------- دوال قاعدة البيانات ---------- +# دوال قاعدة البيانات def get_db(): db = getattr(g, '_database', None) if db is None: @@ -136,7 +133,6 @@ def init_db(): birth_date DATE, is_verified BOOLEAN DEFAULT 0, is_admin BOOLEAN DEFAULT 0, - is_live BOOLEAN DEFAULT 0, coins INTEGER DEFAULT 100, diamonds INTEGER DEFAULT 0, referral_code TEXT UNIQUE, @@ -187,8 +183,8 @@ def init_db(): CREATE TABLE IF NOT EXISTS videos ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, - filename TEXT NOT NULL, - filepath TEXT NOT NULL, + cloudinary_url TEXT NOT NULL, + public_id TEXT NOT NULL, thumbnail TEXT, title TEXT, description TEXT, @@ -208,14 +204,10 @@ def init_db(): challenge_id INTEGER, is_reported BOOLEAN DEFAULT 0, report_count INTEGER DEFAULT 0, - encrypted_path TEXT, - encrypted_key BLOB, - watermarked_path TEXT, nsfw_score REAL DEFAULT 0.0, ai_tags TEXT, processed_for_ai BOOLEAN DEFAULT 0, - FOREIGN KEY(user_id) REFERENCES users(id), - FOREIGN KEY(challenge_id) REFERENCES challenges(id) + FOREIGN KEY(user_id) REFERENCES users(id) ) ''') @@ -231,7 +223,6 @@ def init_db(): saved BOOLEAN DEFAULT 0, reported BOOLEAN DEFAULT 0, watch_time INTEGER DEFAULT 0, - voted_challenge BOOLEAN DEFAULT 0, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(user_id) REFERENCES users(id), FOREIGN KEY(video_id) REFERENCES videos(id), @@ -410,36 +401,6 @@ def init_db(): ) ''') - # جدول البث المباشر - cursor.execute(''' - CREATE TABLE IF NOT EXISTS live_streams ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - stream_key TEXT UNIQUE NOT NULL, - title TEXT, - viewers INTEGER DEFAULT 0, - likes INTEGER DEFAULT 0, - started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - ended_at TIMESTAMP, - is_active BOOLEAN DEFAULT 1, - FOREIGN KEY(user_id) REFERENCES users(id) - ) - ''') - - # جدول هدايا البث المباشر - cursor.execute(''' - CREATE TABLE IF NOT EXISTS live_gifts ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - stream_id INTEGER NOT NULL, - user_id INTEGER NOT NULL, - gift_type TEXT NOT NULL, - gift_value INTEGER NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(stream_id) REFERENCES live_streams(id), - FOREIGN KEY(user_id) REFERENCES users(id) - ) - ''') - # جدول مشاهدات الفيديو cursor.execute(''' CREATE TABLE IF NOT EXISTS video_views ( @@ -516,6 +477,18 @@ def init_db(): ) ''') + # جدول الفيديوهات المحفوظة + cursor.execute(''' + CREATE TABLE IF NOT EXISTS saved_videos ( + user_id INTEGER NOT NULL, + video_id INTEGER NOT NULL, + saved_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(user_id) REFERENCES users(id), + FOREIGN KEY(video_id) REFERENCES videos(id), + PRIMARY KEY (user_id, video_id) + ) + ''') + db.commit() # إنشاء المستخدمين الافتراضيين @@ -523,7 +496,7 @@ def init_db(): create_default_avatars() create_default_challenges() -# ---------- دوال مساعدة ---------- +# دوال مساعدة def generate_referral_code(): return secrets.token_hex(4).upper() @@ -536,14 +509,17 @@ def bytes_to_vector(b): return np.zeros(VECTOR_DIM, dtype=np.float32) return np.frombuffer(b, dtype=np.float32) +def allowed_file(filename, allowed_set): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_set + def allowed_video(filename): - return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_VIDEO_EXTENSIONS + return allowed_file(filename, ALLOWED_VIDEO_EXTENSIONS) def allowed_image(filename): - return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_IMAGE_EXTENSIONS + return allowed_file(filename, ALLOWED_IMAGE_EXTENSIONS) def allowed_plugin(filename): - return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_PLUGIN_EXTENSIONS + return allowed_file(filename, ALLOWED_PLUGIN_EXTENSIONS) def create_default_users(): db = get_db() @@ -595,7 +571,6 @@ def create_default_avatars(): d.text((350, 80), "ARC VIDEO", fill=(255, 45, 85)) cover.save(default_cover_path) except: - # إنشاء ملفات نصية كبديل with open(default_avatar_path, 'w') as f: f.write('default avatar placeholder') with open(default_cover_path, 'w') as f: @@ -626,9 +601,9 @@ def create_default_challenges(): ''', c) db.commit() -# ---------- دوال الذكاء الاصطناعي الفعلية ---------- +# دوال الذكاء الاصطناعي def analyze_video_nsfw(video_path): - """تحليل فيديو للكشف عن محتوى غير مناسب باستخدام OpenCV""" + """تحليل فيديو للكشف عن محتوى غير مناسب""" if not HAS_CV2: return 0.0 @@ -642,7 +617,6 @@ def analyze_video_nsfw(video_path): cap.release() return 0.0 - # أخذ عينات من الفيديو sample_frames = min(10, total_frames) frame_indices = [int(i * total_frames / sample_frames) for i in range(sample_frames)] @@ -651,19 +625,15 @@ def analyze_video_nsfw(video_path): cap.set(cv2.CAP_PROP_POS_FRAMES, idx) ret, frame = cap.read() if ret: - # تحويل إلى HSV للكشف عن ألوان البشرة hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) - # نطاق ألوان البشرة التقريبي lower_skin = np.array([0, 20, 70], dtype=np.uint8) upper_skin = np.array([20, 255, 255], dtype=np.uint8) mask = cv2.inRange(hsv, lower_skin, upper_skin) skin_percentage = (cv2.countNonZero(mask) / (frame.shape[0] * frame.shape[1])) * 100 - if skin_percentage > 30: # إذا كانت نسبة البشرة عالية + if skin_percentage > 30: skin_tone_count += 1 cap.release() - - # حساب درجة NSFW بناءً على نسبة الإطارات التي تحتوي بشرة كثيرة nsfw_score = skin_tone_count / sample_frames return min(nsfw_score, 1.0) @@ -681,24 +651,18 @@ def extract_video_features(video_path): if not cap.isOpened(): return random_vector() - # استخراج إطار واحد وتحويله إلى متجه ret, frame = cap.read() cap.release() if not ret: return random_vector() - # تحويل الإطار إلى تدرج رمادي وتصغير الحجم gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) resized = cv2.resize(gray, (32, 32)) - # تحويل إلى متجه features = resized.flatten().astype(np.float32) - - # تطبيع features = features / 255.0 - # تقليل الأبعاد إلى VECTOR_DIM if len(features) > VECTOR_DIM: step = len(features) // VECTOR_DIM features = features[::step][:VECTOR_DIM] @@ -715,12 +679,10 @@ def generate_auto_tags(video_path, title, description): """توليد وسوم تلقائية من النص وتحليل الفيديو""" tags = set() - # استخراج الكلمات من العنوان والوصف text = f"{title} {description}" words = re.findall(r'#(\w+)', text) tags.update(words) - # استخراج كلمات عربية وإنجليزية all_words = re.findall(r'[\w]+', text) common_tags = ['فيديو', 'ترند', 'مشاهير', 'comedy', 'funny', 'music', 'dance', 'tiktok'] @@ -730,15 +692,12 @@ def generate_auto_tags(video_path, title, description): if word_lower in common_tags or word_lower.isalpha(): tags.add(word_lower) - # إضافة وسوم من تحليل الفيديو إذا كان OpenCV متاحاً if HAS_CV2 and os.path.exists(video_path): try: cap = cv2.VideoCapture(video_path) if cap.isOpened(): - # تقدير نوع المحتوى بناءً على الإطار الأول ret, frame = cap.read() if ret: - # تحليل سطوع الصورة gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) brightness = np.mean(gray) @@ -749,7 +708,6 @@ def generate_auto_tags(video_path, title, description): tags.add('داكن') tags.add('dark') - # تحليل الحواف edges = cv2.Canny(gray, 50, 150) edge_density = np.count_nonzero(edges) / edges.size @@ -764,132 +722,49 @@ def generate_auto_tags(video_path, title, description): except: pass - # تحويل إلى JSON return json.dumps(list(tags)[:15]) -# ---------- دوال التشفير الفعلية ---------- -def encrypt_video_file(input_path, output_path=None, key=None): - """تشفير ملف فيديو باستخدام AES-256""" - if not HAS_CRYPTO: +# دوال Cloudinary +def upload_to_cloudinary(file_path, folder='videos', resource_type='video'): + """رفع ملف إلى Cloudinary""" + if not HAS_CLOUDINARY: return None, None - - if output_path is None: - filename = os.path.basename(input_path) - output_path = os.path.join(ENCRYPTED_FOLDER, f"{filename}.enc") - - if key is None: - key = secrets.token_bytes(32) - try: - with open(input_path, 'rb') as f: - data = f.read() - - cipher = AES.new(key, AES.MODE_CBC) - ct_bytes = cipher.encrypt(pad(data, AES.block_size)) - - # تخزين IV مع البيانات المشفرة - iv = cipher.iv - with open(output_path, 'wb') as f: - f.write(iv + ct_bytes) - - return output_path, key - + response = cloudinary.uploader.upload( + file_path, + folder=folder, + resource_type=resource_type, + eager=[ + {'width': 720, 'height': 1280, 'crop': 'limit'}, + {'width': 360, 'height': 640, 'crop': 'limit'} + ], + eager_async=True + ) + return response['secure_url'], response['public_id'] except Exception as e: - logger.error(f"Encryption error: {e}") + logger.error(f"Cloudinary upload error: {e}") return None, None -def decrypt_video_file(encrypted_path, key, output_path=None): - """فك تشفير فيديو""" - if not HAS_CRYPTO: - return None - - if output_path is None: - output_path = encrypted_path.replace('.enc', '') - - try: - with open(encrypted_path, 'rb') as f: - iv = f.read(16) - ct = f.read() - - cipher = AES.new(key, AES.MODE_CBC, iv=iv) - data = unpad(cipher.decrypt(ct), AES.block_size) - - with open(output_path, 'wb') as f: - f.write(data) - - return output_path - - except Exception as e: - logger.error(f"Decryption error: {e}") - return None - -def add_watermark_to_video(input_path, output_path=None, watermark_text="ARC"): - """إضافة علامة مائية نصية على الفيديو""" - if not HAS_CV2 or not HAS_PIL: - return None - - if output_path is None: - filename = os.path.basename(input_path) - output_path = os.path.join(WATERMARK_FOLDER, filename) - - try: - # فتح الفيديو - cap = cv2.VideoCapture(input_path) - if not cap.isOpened(): - return None - - # الحصول على خصائص الفيديو - fps = int(cap.get(cv2.CAP_PROP_FPS)) - width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) - height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) - - # إنشاء كاتب فيديو - fourcc = cv2.VideoWriter_fourcc(*'mp4v') - out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) - - # إعداد العلامة المائية - font = cv2.FONT_HERSHEY_SIMPLEX - position = (10, height - 20) - - frame_count = 0 - while True: - ret, frame = cap.read() - if not ret: - break - - # إضافة العلامة المائية على كل إطار - cv2.putText(frame, watermark_text, position, font, 1, (255, 255, 255), 2, cv2.LINE_AA) - - # إضافة نص آخر - cv2.putText(frame, f"ARC {datetime.now().year}", (width - 150, 30), font, 0.7, (255, 45, 85), 2) - - out.write(frame) - frame_count += 1 - - # تحديث كل 100 إطار - if frame_count % 100 == 0: - logger.info(f"Watermark progress: {frame_count}/{total_frames}") - - cap.release() - out.release() - cv2.destroyAllWindows() - - return output_path - - except Exception as e: - logger.error(f"Watermark error: {e}") - return None +def get_cloudinary_video_url(public_id, watermark=False): + """الحصول على رابط فيديو من Cloudinary مع إمكانية إضافة علامة مائية""" + if watermark: + return cloudinary.CloudinaryVideo(public_id).build_url( + transformation=[ + {'width': 720, 'height': 1280, 'crop': 'limit'}, + {'overlay': {'font_family': 'Arial', 'font_size': 60, 'text': 'ARC'}, + 'color': '#ff2d55', 'gravity': 'south_east', 'x': 20, 'y': 20} + ] + ) + else: + return cloudinary.CloudinaryVideo(public_id).build_url() -# ---------- دوال نقاط الخبرة والمكافآت ---------- +# دوال نقاط الخبرة والمكافآت def calculate_level_from_xp(xp): - """حساب المستوى بناءً على XP""" if xp < 100: return 1 return int((xp // 100) ** 0.5) + 1 def add_xp(user_id, action, xp_amount): - """إضافة نقاط خبرة للمستخدم""" db = get_db() try: db.execute(''' @@ -899,7 +774,6 @@ def add_xp(user_id, action, xp_amount): db.execute('UPDATE users SET xp = xp + ? WHERE id = ?', (xp_amount, user_id)) - # تحديث المستوى user = db.execute('SELECT xp FROM users WHERE id = ?', (user_id,)).fetchone() if user: new_level = calculate_level_from_xp(user['xp']) @@ -912,12 +786,10 @@ def add_xp(user_id, action, xp_amount): return False def claim_daily_reward(user_id): - """المطالبة بالمكافأة اليومية""" db = get_db() today = datetime.now().date() try: - # التحقق من استلام المكافأة اليوم existing = db.execute(''' SELECT id, streak FROM daily_rewards WHERE user_id = ? AND reward_date = ? @@ -926,7 +798,6 @@ def claim_daily_reward(user_id): if existing: return False, "تم استلام المكافأة اليوم مسبقاً" - # حساب streak yesterday = (today - timedelta(days=1)).isoformat() last = db.execute(''' SELECT streak FROM daily_rewards @@ -934,8 +805,6 @@ def claim_daily_reward(user_id): ''', (user_id, yesterday)).fetchone() streak = (last['streak'] + 1) if last else 1 - - # عملات عشوائية coins = random.randint(50, 100) * streak db.execute(''' @@ -957,9 +826,8 @@ def claim_daily_reward(user_id): logger.error(f"Error claiming daily reward: {e}") return False, str(e) -# ---------- دوال التسويق بالعمولة ---------- +# دوال التسويق بالعمولة def generate_affiliate_link(user_id): - """توليد رابط إحالة فريد""" code = secrets.token_urlsafe(8) db = get_db() db.execute('UPDATE users SET referral_code = ? WHERE id = ?', (code, user_id)) @@ -967,7 +835,6 @@ def generate_affiliate_link(user_id): return f"/register?ref={code}" def track_affiliate_click(referral_code, ip, user_agent): - """تسجيل نقرة على رابط الإحالة""" db = get_db() try: referrer = db.execute('SELECT id FROM users WHERE referral_code = ?', (referral_code,)).fetchone() @@ -982,7 +849,6 @@ def track_affiliate_click(referral_code, ip, user_agent): db.execute('UPDATE users SET affiliate_clicks = affiliate_clicks + 1 WHERE id = ?', (referrer['id'],)) db.commit() - # تسجيل في ملف log_file = os.path.join(AFFILIATE_LOG_FOLDER, f"clicks_{datetime.now().strftime('%Y%m')}.log") with open(log_file, 'a') as f: f.write(f"{datetime.now().isoformat()}|{referrer['id']}|{ip}|{user_agent}\n") @@ -994,14 +860,12 @@ def track_affiliate_click(referral_code, ip, user_agent): return False def process_affiliate_conversion(referral_code, new_user_id): - """معالجة تحويل الإحالة عند تسجيل مستخدم جديد""" db = get_db() try: referrer = db.execute('SELECT id FROM users WHERE referral_code = ?', (referral_code,)).fetchone() if not referrer: return - # تحديث آخر نقرة db.execute(''' UPDATE affiliate_clicks SET converted = 1 WHERE referrer_id = ? AND converted = 0 @@ -1010,7 +874,6 @@ def process_affiliate_conversion(referral_code, new_user_id): db.execute('UPDATE users SET affiliate_conversions = affiliate_conversions + 1 WHERE id = ?', (referrer['id'],)) - # مكافآت coins_referrer = 100 coins_referred = 50 @@ -1035,7 +898,6 @@ def process_affiliate_conversion(referral_code, new_user_id): db.commit() - # تسجيل في ملف log_file = os.path.join(AFFILIATE_LOG_FOLDER, f"conversions_{datetime.now().strftime('%Y%m')}.log") with open(log_file, 'a') as f: f.write(f"{datetime.now().isoformat()}|{referrer['id']}|{new_user_id}\n") @@ -1043,9 +905,8 @@ def process_affiliate_conversion(referral_code, new_user_id): except Exception as e: logger.error(f"Error processing affiliate conversion: {e}") -# ---------- دوال التحليلات ---------- +# دوال التحليلات def track_event(user_id, event_name, event_data=None): - """تسجيل حدث تحليلي""" db = get_db() try: db.execute(''' @@ -1057,7 +918,6 @@ def track_event(user_id, event_name, event_data=None): logger.error(f"Error tracking event: {e}") def get_user_stats(user_id): - """الحصول على إحصائيات المستخدم""" db = get_db() try: videos = db.execute('SELECT COUNT(*) FROM videos WHERE user_id = ?', (user_id,)).fetchone()[0] @@ -1091,9 +951,8 @@ def get_user_stats(user_id): logger.error(f"Error getting user stats: {e}") return {} -# ---------- دوال المطور ---------- +# دوال المطور def create_developer_session(user_id): - """إنشاء جلسة مطور جديدة""" token = secrets.token_urlsafe(32) expires = datetime.now() + timedelta(hours=1) db = get_db() @@ -1105,7 +964,6 @@ def create_developer_session(user_id): return token def validate_developer_session(token): - """التحقق من صحة جلسة المطور""" db = get_db() session = db.execute(''' SELECT user_id, expires_at FROM developer_sessions @@ -1117,10 +975,8 @@ def validate_developer_session(token): return None def execute_code_in_sandbox(code): - """تنفيذ كود Python في بيئة معزولة""" local_vars = {} try: - # إضافة بعض الدوال المساعدة allowed_builtins = { 'print': print, 'len': len, @@ -1148,12 +1004,10 @@ def execute_code_in_sandbox(code): exec(code, {"__builtins__": allowed_builtins}, local_vars) - # البحث عن نتيجة result = None if 'result' in local_vars: result = local_vars['result'] elif len(local_vars) > 0: - # آخر متغير تم تعريفه last_var = list(local_vars.keys())[-1] result = local_vars[last_var] @@ -1163,10 +1017,8 @@ def execute_code_in_sandbox(code): return {"success": False, "error": str(e)} def install_plugin(user_id, name, code): - """تثبيت إضافة جديدة""" db = get_db() try: - # حذف القديم إن وجد db.execute('DELETE FROM developer_plugins WHERE user_id = ? AND name = ?', (user_id, name)) db.execute(''' @@ -1176,7 +1028,6 @@ def install_plugin(user_id, name, code): db.commit() - # تحميل في الذاكرة load_plugin_into_memory(user_id, name, code) return True @@ -1186,7 +1037,6 @@ def install_plugin(user_id, name, code): return False def load_plugin_into_memory(user_id, name, code): - """تحميل إضافة في الذاكرة""" try: namespace = {} exec(code, namespace) @@ -1198,7 +1048,6 @@ def load_plugin_into_memory(user_id, name, code): return False def load_all_plugins(): - """تحميل جميع الإضافات النشطة""" db = get_db() try: plugins = db.execute('SELECT user_id, name, code FROM developer_plugins WHERE is_active = 1').fetchall() @@ -1209,7 +1058,6 @@ def load_all_plugins(): logger.error(f"Error loading plugins: {e}") def call_plugin_hook(user_id, hook_name, *args, **kwargs): - """استدعاء خطاف من إضافات المستخدم""" results = [] for key, namespace in developer_plugins.items(): uid = int(key.split('_')[0]) @@ -1221,7 +1069,7 @@ def call_plugin_hook(user_id, hook_name, *args, **kwargs): logger.error(f"Plugin hook {hook_name} error: {e}") return results -# ---------- دوال الإشعارات والعملات ---------- +# دوال الإشعارات والعملات def add_notification(user_id, from_user_id, n_type, content, video_id=None, comment_id=None): if user_id == from_user_id: return @@ -1263,7 +1111,7 @@ def deduct_coins(user_id, amount, description): return True return False -# ---------- نظام التوصيات ---------- +# نظام التوصيات def get_user_interest_vector(user_id): db = get_db() rows = db.execute(''' @@ -1283,14 +1131,16 @@ def get_user_interest_vector(user_id): return np.mean(vectors, axis=0) -def recommend_videos(user_id, limit=10, offset=0): +def recommend_videos(user_id, feed_type='for-you', limit=10, offset=0): db = get_db() try: watched = db.execute('SELECT video_id FROM interactions WHERE user_id = ? AND watched = 1', (user_id,)).fetchall() watched_ids = [w['video_id'] for w in watched] if watched else [-1] - following = db.execute('SELECT user_id FROM follows WHERE follower_id = ?', (user_id,)).fetchall() - following_ids = [f['user_id'] for f in following] if following else [-1] + following_ids = [] + if feed_type == 'friends': + following = db.execute('SELECT user_id FROM follows WHERE follower_id = ?', (user_id,)).fetchall() + following_ids = [f['user_id'] for f in following] if following else [-1] interest = get_user_interest_vector(user_id) @@ -1303,42 +1153,47 @@ def recommend_videos(user_id, limit=10, offset=0): JOIN users u ON v.user_id = u.id WHERE v.id NOT IN ({placeholders}) AND v.visibility = 'public' - ORDER BY v.upload_time DESC - LIMIT ? OFFSET ? ''' - params = watched_ids + [limit * 3, offset] + params = watched_ids + + if feed_type == 'friends': + query += f' AND v.user_id IN ({",".join(["?"] * len(following_ids))})' + params += following_ids + + query += ' ORDER BY v.upload_time DESC LIMIT ? OFFSET ?' + params += [limit * 3, offset] + rows = db.execute(query, params).fetchall() - video_scores = [] - for row in rows: - score = 10.0 - - # تشابه المتجهات - if not np.all(interest == 0): - vec_row = db.execute('SELECT vector FROM videos WHERE id = ?', (row['id'],)).fetchone() - if vec_row and vec_row['vector']: - vec = bytes_to_vector(vec_row['vector']) - similarity = 1.0 / (1.0 + np.linalg.norm(interest - vec)) - score += similarity * 30 - - # الشعبية - popularity = (row['views'] * 2 + row['likes_count'] * 5 + row['comments_count'] * 3) / 100 - score += min(popularity, 40) - - # الحداثة - upload_time = datetime.strptime(row['upload_time'], '%Y-%m-%d %H:%M:%S') - hours_ago = (datetime.now() - upload_time).total_seconds() / 3600 - recency = max(0, 20 - (hours_ago / 72)) - score += recency - - # المتابعة - if row['user_id'] in following_ids: - score += 10 + if feed_type == 'for-you': + video_scores = [] + for row in rows: + score = 10.0 + + if not np.all(interest == 0): + vec_row = db.execute('SELECT vector FROM videos WHERE id = ?', (row['id'],)).fetchone() + if vec_row and vec_row['vector']: + vec = bytes_to_vector(vec_row['vector']) + similarity = 1.0 / (1.0 + np.linalg.norm(interest - vec)) + score += similarity * 30 + + popularity = (row['views'] * 2 + row['likes_count'] * 5 + row['comments_count'] * 3) / 100 + score += min(popularity, 40) + + upload_time = datetime.strptime(row['upload_time'], '%Y-%m-%d %H:%M:%S') + hours_ago = (datetime.now() - upload_time).total_seconds() / 3600 + recency = max(0, 20 - (hours_ago / 72)) + score += recency + + if row['user_id'] in following_ids: + score += 10 + + video_scores.append((score, dict(row))) - video_scores.append((score, dict(row))) - - video_scores.sort(key=lambda x: x[0], reverse=True) - return [v for _, v in video_scores[:limit]] + video_scores.sort(key=lambda x: x[0], reverse=True) + return [v for _, v in video_scores[:limit]] + else: + return [dict(row) for row in rows[:limit]] except Exception as e: logger.error(f"Error in recommendation: {e}") @@ -1352,7 +1207,7 @@ def recommend_videos(user_id, limit=10, offset=0): ''', (limit, offset)).fetchall() return [dict(row) for row in rows] -# ---------- ديكوريتورات ---------- +# ديكوريتورات def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): @@ -1387,12 +1242,12 @@ def developer_required(f): return f(*args, **kwargs) return decorated_function -# ---------- صفحات تسجيل الدخول والتسجيل ---------- +# صفحات تسجيل الدخول والتسجيل @app.route('/') def index(): if 'user_id' not in session: return redirect(url_for('login')) - return render_template_string(MAIN_TEMPLATE) + return render_template_string(SPA_TEMPLATE) @app.route('/login', methods=['GET', 'POST']) def login(): @@ -1431,25 +1286,33 @@ def login(): @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': - username = request.form.get('username', '').strip() - email = request.form.get('email', '').strip() - password = request.form.get('password', '') - confirm = request.form.get('confirm_password', '') - referral = request.form.get('referral_code', '').strip() + if request.is_json: + data = request.get_json() + username = data.get('username', '').strip() + email = data.get('email', '').strip() + password = data.get('password', '') + confirm = data.get('confirm_password', '') + referral = data.get('referral_code', '').strip() + else: + username = request.form.get('username', '').strip() + email = request.form.get('email', '').strip() + password = request.form.get('password', '') + confirm = request.form.get('confirm_password', '') + referral = request.form.get('referral_code', '').strip() if not username or not email or not password: - return render_template_string(REGISTER_PAGE, error='جميع الحقول مطلوبة') + return jsonify({'error': 'جميع الحقول مطلوبة'}), 400 if password != confirm: - return render_template_string(REGISTER_PAGE, error='كلمات المرور غير متطابقة') + return jsonify({'error': 'كلمات المرور غير متطابقة'}), 400 if len(password) < 6: - return render_template_string(REGISTER_PAGE, error='كلمة المرور يجب أن تكون 6 أحرف على الأقل') + return jsonify({'error': 'كلمة المرور يجب أن تكون 6 أحرف على الأقل'}), 400 db = get_db() existing = db.execute('SELECT id FROM users WHERE username = ? OR email = ?', (username, email)).fetchone() if existing: - return render_template_string(REGISTER_PAGE, error='اسم المستخدم أو البريد الإلكتروني موجود بالفعل') + return jsonify({'error': 'اسم المستخدم أو البريد الإلكتروني موجود بالفعل'}), 400 hashed = generate_password_hash(password) referral_code = generate_referral_code() @@ -1462,17 +1325,18 @@ def register(): user_id = db.execute("SELECT last_insert_rowid()").fetchone()[0] db.execute('INSERT INTO user_settings (user_id) VALUES (?)', (user_id,)) - # معالجة الإحالة if referral: referrer = db.execute('SELECT id FROM users WHERE referral_code = ?', (referral,)).fetchone() if referrer: process_affiliate_conversion(referral, user_id) - # XP للمستخدم الجديد add_xp(user_id, 'register', 50) track_event(user_id, 'register', {'referral': referral if referral else None}) db.commit() + + if request.is_json: + return jsonify({'success': True, 'redirect': '/login'}) return redirect(url_for('login')) return render_template_string(REGISTER_PAGE) @@ -1490,23 +1354,23 @@ def forgot_password(): return render_template_string(FORGOT_PAGE, message='تم إرسال رابط إعادة التعيين إلى بريدك الإلكتروني') return render_template_string(FORGOT_PAGE) -# ---------- ملف المستخدم ---------- -@app.route('/profile/') +# مسارات API للملف الشخصي +@app.route('/api/profile/') @login_required -def profile(user_id): +def api_profile(user_id): db = get_db() user = db.execute(''' - SELECT u.*, s.* + SELECT u.*, s.privacy_profile, s.privacy_videos FROM users u LEFT JOIN user_settings s ON u.id = s.user_id WHERE u.id = ? ''', (user_id,)).fetchone() if not user: - abort(404) + return jsonify({'error': 'User not found'}), 404 videos = db.execute(''' - SELECT id, filename, title, thumbnail, views, likes_count, comments_count, upload_time + SELECT id, title, cloudinary_url, thumbnail, views, likes_count, comments_count, upload_time FROM videos WHERE user_id = ? AND visibility = 'public' ORDER BY upload_time DESC LIMIT 30 ''', (user_id,)).fetchall() @@ -1525,85 +1389,51 @@ def profile(user_id): videos_list = [] for v in videos: video_dict = dict(v) - video_dict['url'] = f'/videos/{v["filename"]}' videos_list.append(video_dict) - return render_template_string(PROFILE_TEMPLATE, - user=dict(user), - videos=videos_list, - followers_count=followers_count, - following_count=following_count, - is_following=is_following, - total_views=total_views, - total_likes=total_likes) - -@app.route('/settings', methods=['GET', 'POST']) + return jsonify({ + 'id': user['id'], + 'username': user['username'], + 'avatar': f"/avatars/{user['avatar']}", + 'cover': f"/avatars/{user['cover_image']}", + 'bio': user['bio'], + 'is_verified': user['is_verified'], + 'is_following': is_following, + 'followers': followers_count, + 'following': following_count, + 'videos_count': len(videos_list), + 'total_views': total_views, + 'total_likes': total_likes, + 'videos': videos_list + }) + +@app.route('/api/profile//saved') @login_required -def settings(): - db = get_db() +def api_saved_videos(user_id): + if user_id != session['user_id']: + return jsonify({'error': 'Forbidden'}), 403 - if request.method == 'POST': - bio = request.form.get('bio', '') - phone = request.form.get('phone', '') - gender = request.form.get('gender', '') - country = request.form.get('country', '') - birth_date = request.form.get('birth_date', '') - - avatar = request.files.get('avatar') - cover = request.files.get('cover') - - if avatar and avatar.filename and allowed_image(avatar.filename): - ext = avatar.filename.rsplit('.', 1)[1].lower() - filename = f"avatar_{session['user_id']}.{ext}" - filepath = os.path.join(AVATAR_FOLDER, filename) - avatar.save(filepath) - db.execute('UPDATE users SET avatar = ? WHERE id = ?', (filename, session['user_id'])) - - if cover and cover.filename and allowed_image(cover.filename): - ext = cover.filename.rsplit('.', 1)[1].lower() - filename = f"cover_{session['user_id']}.{ext}" - filepath = os.path.join(AVATAR_FOLDER, filename) - cover.save(filepath) - db.execute('UPDATE users SET cover_image = ? WHERE id = ?', (filename, session['user_id'])) - - db.execute(''' - UPDATE users SET bio = ?, phone = ?, gender = ?, country = ?, birth_date = ? - WHERE id = ? - ''', (bio, phone, gender, country, birth_date, session['user_id'])) - - privacy_profile = request.form.get('privacy_profile', 'public') - privacy_videos = request.form.get('privacy_videos', 'public') - notifications_likes = request.form.get('notifications_likes') == 'on' - notifications_comments = request.form.get('notifications_comments') == 'on' - notifications_follows = request.form.get('notifications_follows') == 'on' - notifications_messages = request.form.get('notifications_messages') == 'on' - dark_mode = request.form.get('dark_mode') == 'on' - language = request.form.get('language', 'ar') - - db.execute(''' - UPDATE user_settings SET - privacy_profile = ?, privacy_videos = ?, - notifications_likes = ?, notifications_comments = ?, - notifications_follows = ?, notifications_messages = ?, - dark_mode = ?, language = ? - WHERE user_id = ? - ''', (privacy_profile, privacy_videos, notifications_likes, notifications_comments, - notifications_follows, notifications_messages, dark_mode, language, session['user_id'])) - - db.commit() - return redirect(url_for('profile', user_id=session['user_id'])) + db = get_db() + videos = db.execute(''' + SELECT v.*, u.username, u.avatar + FROM saved_videos sv + JOIN videos v ON sv.video_id = v.id + JOIN users u ON v.user_id = u.id + WHERE sv.user_id = ? + ORDER BY sv.saved_at DESC + ''', (user_id,)).fetchall() - user = db.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],)).fetchone() - settings = db.execute('SELECT * FROM user_settings WHERE user_id = ?', (session['user_id'],)).fetchone() + result = [] + for v in videos: + video = dict(v) + video['avatar_url'] = f"/avatars/{v['avatar']}" + result.append(video) - return render_template_string(SETTINGS_TEMPLATE, - user=dict(user), - settings=dict(settings) if settings else {}) + return jsonify(result) -# ---------- نظام المتابعة ---------- -@app.route('/follow/', methods=['POST']) +@app.route('/api/follow/', methods=['POST']) @login_required -def follow(user_id): +def api_follow(user_id): if user_id == session['user_id']: return jsonify({'error': 'لا يمكنك متابعة نفسك'}), 400 @@ -1616,15 +1446,14 @@ def follow(user_id): count = db.execute('SELECT COUNT(*) FROM follows WHERE user_id = ?', (user_id,)).fetchone()[0] - # XP للمتابعة add_xp(session['user_id'], 'follow', 5) track_event(session['user_id'], 'follow', {'followed_user_id': user_id}) return jsonify({'status': 'ok', 'followers_count': count}) -@app.route('/unfollow/', methods=['POST']) +@app.route('/api/unfollow/', methods=['POST']) @login_required -def unfollow(user_id): +def api_unfollow(user_id): db = get_db() db.execute('DELETE FROM follows WHERE user_id = ? AND follower_id = ?', (user_id, session['user_id'])) db.commit() @@ -1634,61 +1463,63 @@ def unfollow(user_id): return jsonify({'status': 'ok', 'followers_count': count}) -@app.route('/api/followers/') -@login_required -def get_followers(user_id): - db = get_db() - followers = db.execute(''' - SELECT u.id, u.username, u.avatar, u.is_verified - FROM follows f - JOIN users u ON f.follower_id = u.id - WHERE f.user_id = ? - ORDER BY f.created_at DESC - ''', (user_id,)).fetchall() - - result = [] - for f in followers: - follower = dict(f) - follower['avatar_url'] = f'/avatars/{f["avatar"]}' - result.append(follower) - - return jsonify(result) - -@app.route('/api/following/') +# مسارات الفيديو +@app.route('/api/feed/') @login_required -def get_following(user_id): - db = get_db() - following = db.execute(''' - SELECT u.id, u.username, u.avatar, u.is_verified - FROM follows f - JOIN users u ON f.user_id = u.id - WHERE f.follower_id = ? - ORDER BY f.created_at DESC - ''', (user_id,)).fetchall() - - result = [] - for f in following: - follow = dict(f) - follow['avatar_url'] = f'/avatars/{f["avatar"]}' - result.append(follow) +def api_feed(feed_type): + try: + page = int(request.args.get('page', 1)) + limit = 10 + offset = (page - 1) * limit + + videos = recommend_videos(session['user_id'], feed_type, limit, offset) + + result = [] + for v in videos: + video = {} + for key, value in v.items(): + if key != 'vector' and not isinstance(value, bytes): + video[key] = value + elif key == 'vector': + continue + else: + try: + video[key] = str(value) + except: + video[key] = None + + video['avatar_url'] = f"/avatars/{v['avatar']}" + + db = get_db() + interaction = db.execute(''' + SELECT liked FROM interactions WHERE user_id = ? AND video_id = ? + ''', (session['user_id'], v['id'])).fetchone() + + video['liked_by_user'] = bool(interaction['liked']) if interaction else False + + saved = db.execute('SELECT * FROM saved_videos WHERE user_id = ? AND video_id = ?', + (session['user_id'], v['id'])).fetchone() + video['saved_by_user'] = saved is not None + + result.append(video) + + track_event(session['user_id'], f'view_feed_{feed_type}', {'page': page}) + + return jsonify({'videos': result, 'next_page': page + 1 if len(result) == limit else None}) - return jsonify(result) + except Exception as e: + logger.error(f"Error in feed: {e}") + return jsonify({'videos': [], 'next_page': None, 'error': str(e)}), 500 -# ---------- نظام الفيديوهات ---------- -@app.route('/upload', methods=['GET', 'POST']) +@app.route('/api/upload', methods=['POST']) @login_required -def upload_video(): - if request.method == 'GET': - db = get_db() - challenges = db.execute('SELECT * FROM challenges WHERE is_active = 1 AND end_date > DATE()').fetchall() - return render_template_string(UPLOAD_TEMPLATE, challenges=[dict(c) for c in challenges]) - +def api_upload(): if 'video' not in request.files: - return 'لم يتم اختيار فيديو', 400 + return jsonify({'error': 'لم يتم اختيار فيديو'}), 400 file = request.files['video'] if file.filename == '': - return 'لم يتم اختيار فيديو', 400 + return jsonify({'error': 'لم يتم اختيار فيديو'}), 400 title = request.form.get('title', '') description = request.form.get('description', '') @@ -1702,35 +1533,38 @@ def upload_video(): if file and allowed_video(file.filename): filename = secure_filename(file.filename) unique_name = f"{uuid.uuid4().hex}_{filename}" - filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_name) - file.save(filepath) + temp_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_name) + file.save(temp_path) # استخراج الخصائص - vec_bytes = extract_video_features(filepath) + vec_bytes = extract_video_features(temp_path) # تحليل NSFW - nsfw_score = analyze_video_nsfw(filepath) if HAS_CV2 else 0.0 + nsfw_score = analyze_video_nsfw(temp_path) if HAS_CV2 else 0.0 # توليد وسوم تلقائية - ai_tags = generate_auto_tags(filepath, title, description) + ai_tags = generate_auto_tags(temp_path, title, description) - # تشفير الفيديو - encrypted_path, enc_key = encrypt_video_file(filepath) if HAS_CRYPTO else (None, None) - enc_key_bytes = enc_key if enc_key else None + # رفع إلى Cloudinary + cloud_url, public_id = upload_to_cloudinary(temp_path) - # إضافة علامة مائية - watermarked_path = add_watermark_to_video(filepath) if HAS_CV2 and HAS_PIL else None + if not cloud_url: + os.remove(temp_path) + return jsonify({'error': 'فشل رفع الفيديو إلى السحابة'}), 500 + + # حذف الملف المؤقت + os.remove(temp_path) db = get_db() cursor = db.execute(''' INSERT INTO videos ( - user_id, filename, filepath, title, description, music, + user_id, cloudinary_url, public_id, title, description, music, allow_comments, allow_duet, allow_stitch, visibility, vector, challenge_id, - nsfw_score, ai_tags, encrypted_path, encrypted_key, watermarked_path - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', (session['user_id'], unique_name, filepath, title, description, music, + nsfw_score, ai_tags + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', (session['user_id'], cloud_url, public_id, title, description, music, allow_comments, allow_duet, allow_stitch, visibility, vec_bytes, challenge_id, - nsfw_score, ai_tags, encrypted_path, enc_key_bytes, watermarked_path)) + nsfw_score, ai_tags)) video_id = cursor.lastrowid @@ -1754,187 +1588,19 @@ def upload_video(): db.commit() - # مكافآت add_coins(session['user_id'], 10, 'مكافأة نشر فيديو') add_xp(session['user_id'], 'upload_video', 20) track_event(session['user_id'], 'upload_video', {'video_id': video_id}) - # استدعاء خطافات الإضافات call_plugin_hook(session['user_id'], 'on_video_upload', video_id) - return redirect(url_for('index')) - - return 'نوع الملف غير مدعوم', 400 - -@app.route('/video/') -@login_required -def video_page(video_id): - return render_template_string(VIDEO_PAGE_TEMPLATE, video_id=video_id) - -@app.route('/api/for-you') -@login_required -def for_you(): - try: - page = int(request.args.get('page', 1)) - limit = 10 - offset = (page - 1) * limit - - videos = recommend_videos(session['user_id'], limit, offset) - - result = [] - for v in videos: - video = {} - for key, value in v.items(): - if key != 'vector' and not isinstance(value, bytes): - video[key] = value - elif key == 'vector': - continue - else: - try: - video[key] = str(value) - except: - video[key] = None - - video['url'] = f'/videos/{v["filename"]}' - video['avatar_url'] = f'/avatars/{v["avatar"]}' - - db = get_db() - interaction = db.execute(''' - SELECT liked FROM interactions WHERE user_id = ? AND video_id = ? - ''', (session['user_id'], v['id'])).fetchone() - - video['liked_by_user'] = bool(interaction['liked']) if interaction else False - result.append(video) - - track_event(session['user_id'], 'view_for_you', {'page': page}) - - return jsonify({'videos': result, 'next_page': page + 1 if len(result) == limit else None}) - - except Exception as e: - logger.error(f"Error in for_you: {e}") - return jsonify({'videos': [], 'next_page': None, 'error': str(e)}), 500 - -@app.route('/api/trending') -@login_required -def trending(): - try: - page = int(request.args.get('page', 1)) - limit = 10 - offset = (page - 1) * limit - - db = get_db() - videos = db.execute(''' - SELECT v.*, u.username, u.avatar, u.is_verified, - (SELECT COUNT(*) FROM interactions WHERE video_id = v.id AND liked = 1) as likes - FROM videos v - JOIN users u ON v.user_id = u.id - WHERE v.visibility = 'public' - ORDER BY v.views DESC, v.likes_count DESC - LIMIT ? OFFSET ? - ''', (limit, offset)).fetchall() - - result = [] - for v in videos: - video = dict(v) - video['url'] = f'/videos/{v["filename"]}' - video['avatar_url'] = f'/avatars/{v["avatar"]}' - video.pop('vector', None) - - interaction = db.execute(''' - SELECT liked FROM interactions WHERE user_id = ? AND video_id = ? - ''', (session['user_id'], v['id'])).fetchone() - - video['liked_by_user'] = bool(interaction['liked']) if interaction else False - result.append(video) - - return jsonify({'videos': result, 'next_page': page + 1 if len(result) == limit else None}) - - except Exception as e: - logger.error(f"Error in trending: {e}") - return jsonify({'videos': [], 'next_page': None, 'error': str(e)}), 500 - -@app.route('/api/search') -@login_required -def search(): - try: - q = request.args.get('q', '').strip() - - if not q or len(q) < 1: - db = get_db() - videos = db.execute(''' - SELECT v.*, u.username, u.avatar - FROM videos v - JOIN users u ON v.user_id = u.id - WHERE v.visibility = 'public' - ORDER BY RANDOM() - LIMIT 30 - ''').fetchall() - - result = [] - for v in videos: - video = dict(v) - video['url'] = f'/videos/{v["filename"]}' - video['avatar_url'] = f'/avatars/{v["avatar"]}' - video.pop('vector', None) - result.append(video) - - return jsonify({'videos': result}) - - else: - db = get_db() - - # بحث في الفيديوهات - videos = db.execute(''' - SELECT v.*, u.username, u.avatar - FROM videos v - JOIN users u ON v.user_id = u.id - WHERE v.visibility = 'public' - AND (v.title LIKE ? OR v.description LIKE ?) - ORDER BY v.views DESC - LIMIT 30 - ''', ('%'+q+'%', '%'+q+'%')).fetchall() - - # بحث في الهاشتاغات - hashtag = db.execute('SELECT id FROM hashtags WHERE tag LIKE ?', ('%'+q+'%',)).fetchone() - if hashtag: - hashtag_videos = db.execute(''' - SELECT v.*, u.username, u.avatar - FROM videos v - JOIN users u ON v.user_id = u.id - JOIN video_hashtags vh ON v.id = vh.video_id - WHERE vh.hashtag_id = ? AND v.visibility = 'public' - ORDER BY v.views DESC - LIMIT 20 - ''', (hashtag['id'],)).fetchall() - videos = list(videos) + list(hashtag_videos) - - # إزالة التكرار - seen = set() - unique_videos = [] - for v in videos: - if v['id'] not in seen: - seen.add(v['id']) - unique_videos.append(v) - - result = [] - for v in unique_videos[:30]: - video = dict(v) - video['url'] = f'/videos/{v["filename"]}' - video['avatar_url'] = f'/avatars/{v["avatar"]}' - video.pop('vector', None) - result.append(video) - - track_event(session['user_id'], 'search', {'query': q, 'results': len(result)}) - - return jsonify({'videos': result}) + return jsonify({'success': True, 'video_id': video_id}) - except Exception as e: - logger.error(f"Error in search: {e}") - return jsonify({'videos': []}), 500 + return jsonify({'error': 'نوع الملف غير مدعوم'}), 400 @app.route('/api/like/', methods=['POST']) @login_required -def like_video(video_id): +def api_like(video_id): try: user_id = session['user_id'] db = get_db() @@ -1943,12 +1609,10 @@ def like_video(video_id): (user_id, video_id)).fetchone() if interaction and interaction['liked']: - # إلغاء الإعجاب db.execute('UPDATE interactions SET liked = 0 WHERE user_id = ? AND video_id = ?', (user_id, video_id)) db.execute('UPDATE videos SET likes_count = likes_count - 1 WHERE id = ?', (video_id,)) liked = False else: - # إعجاب db.execute(''' INSERT INTO interactions (user_id, video_id, liked, watched) VALUES (?, ?, 1, 1) @@ -1980,7 +1644,7 @@ def like_video(video_id): @app.route('/api/view/', methods=['POST']) @login_required -def view_video(video_id): +def api_view(video_id): try: user_id = session['user_id'] data = request.get_json() or {} @@ -2013,7 +1677,6 @@ def view_video(video_id): db.commit() - # XP للمشاهدة (مرة واحدة فقط لكل مشاهدة كاملة) if completed: add_xp(user_id, 'complete_view', 3) @@ -2025,61 +1688,28 @@ def view_video(video_id): logger.error(f"Error in view_video: {e}") return jsonify({'error': str(e)}), 500 -@app.route('/api/share/', methods=['POST']) +@app.route('/api/save/', methods=['POST']) @login_required -def share_video(video_id): +def api_save(video_id): try: user_id = session['user_id'] db = get_db() - db.execute(''' - INSERT INTO interactions (user_id, video_id, shared, watched) - VALUES (?, ?, 1, 1) - ON CONFLICT(user_id, video_id) DO UPDATE SET shared = 1 - ''', (user_id, video_id)) + saved = db.execute('SELECT * FROM saved_videos WHERE user_id = ? AND video_id = ?', + (user_id, video_id)).fetchone() + + if saved: + db.execute('DELETE FROM saved_videos WHERE user_id = ? AND video_id = ?', (user_id, video_id)) + db.execute('UPDATE videos SET saves_count = saves_count - 1 WHERE id = ?', (video_id,)) + saved_status = False + else: + db.execute('INSERT INTO saved_videos (user_id, video_id) VALUES (?, ?)', (user_id, video_id)) + db.execute('UPDATE videos SET saves_count = saves_count + 1 WHERE id = ?', (video_id,)) + saved_status = True - db.execute('UPDATE videos SET shares_count = shares_count + 1 WHERE id = ?', (video_id,)) db.commit() - shares = db.execute('SELECT shares_count FROM videos WHERE id = ?', (video_id,)).fetchone()['shares_count'] - - # مكافآت - add_coins(user_id, 5, 'مكافأة مشاركة فيديو') - add_xp(user_id, 'share', 3) - track_event(user_id, 'share_video', {'video_id': video_id}) - - return jsonify({'status': 'ok', 'shares_count': shares}) - - except Exception as e: - logger.error(f"Error in share_video: {e}") - return jsonify({'error': str(e)}), 500 - -@app.route('/api/save/', methods=['POST']) -@login_required -def save_video(video_id): - try: - user_id = session['user_id'] - db = get_db() - - saved = db.execute('SELECT saved FROM interactions WHERE user_id = ? AND video_id = ?', - (user_id, video_id)).fetchone() - - if saved and saved['saved']: - db.execute('UPDATE interactions SET saved = 0 WHERE user_id = ? AND video_id = ?', (user_id, video_id)) - db.execute('UPDATE videos SET saves_count = saves_count - 1 WHERE id = ?', (video_id,)) - saved_status = False - else: - db.execute(''' - INSERT INTO interactions (user_id, video_id, saved, watched) - VALUES (?, ?, 1, 1) - ON CONFLICT(user_id, video_id) DO UPDATE SET saved = 1 - ''', (user_id, video_id)) - db.execute('UPDATE videos SET saves_count = saves_count + 1 WHERE id = ?', (video_id,)) - saved_status = True - - db.commit() - - saves = db.execute('SELECT saves_count FROM videos WHERE id = ?', (video_id,)).fetchone()['saves_count'] + saves = db.execute('SELECT saves_count FROM videos WHERE id = ?', (video_id,)).fetchone()['saves_count'] track_event(user_id, 'save', {'video_id': video_id, 'saved': saved_status}) @@ -2089,138 +1719,106 @@ def save_video(video_id): logger.error(f"Error in save_video: {e}") return jsonify({'error': str(e)}), 500 -@app.route('/api/saved') -@login_required -def get_saved_videos(): - try: - db = get_db() - videos = db.execute(''' - SELECT v.*, u.username, u.avatar - FROM interactions i - JOIN videos v ON i.video_id = v.id - JOIN users u ON v.user_id = u.id - WHERE i.user_id = ? AND i.saved = 1 - ORDER BY i.timestamp DESC - ''', (session['user_id'],)).fetchall() - - result = [] - for v in videos: - video = dict(v) - video['url'] = f'/videos/{v["filename"]}' - video['avatar_url'] = f'/avatars/{v["avatar"]}' - video.pop('vector', None) - result.append(video) - - return jsonify(result) - - except Exception as e: - logger.error(f"Error in get_saved_videos: {e}") - return jsonify([]), 500 - -# ---------- نظام التعليقات ---------- -@app.route('/api/comments/') +@app.route('/api/comments/', methods=['GET', 'POST']) @login_required -def get_comments(video_id): - try: - db = get_db() - - comments = db.execute(''' - SELECT c.*, u.username, u.avatar, u.is_verified, - (SELECT COUNT(*) FROM comment_likes WHERE comment_id = c.id) as likes_count, - (SELECT COUNT(*) FROM comments WHERE parent_id = c.id) as replies_count - FROM comments c - JOIN users u ON c.user_id = u.id - WHERE c.video_id = ? AND c.parent_id IS NULL - ORDER BY c.is_pinned DESC, c.likes_count DESC, c.timestamp DESC - LIMIT 50 - ''', (video_id,)).fetchall() - - result = [] - for comment in comments: - c = dict(comment) +def api_comments(video_id): + if request.method == 'GET': + try: + db = get_db() - replies = db.execute(''' + comments = db.execute(''' SELECT c.*, u.username, u.avatar, u.is_verified, - (SELECT COUNT(*) FROM comment_likes WHERE comment_id = c.id) as likes_count + (SELECT COUNT(*) FROM comment_likes WHERE comment_id = c.id) as likes_count, + (SELECT COUNT(*) FROM comments WHERE parent_id = c.id) as replies_count FROM comments c JOIN users u ON c.user_id = u.id - WHERE c.parent_id = ? - ORDER BY c.timestamp ASC - LIMIT 10 - ''', (comment['id'],)).fetchall() + WHERE c.video_id = ? AND c.parent_id IS NULL + ORDER BY c.is_pinned DESC, c.likes_count DESC, c.timestamp DESC + LIMIT 50 + ''', (video_id,)).fetchall() - c['replies'] = [] - for r in replies: - reply = dict(r) + result = [] + for comment in comments: + c = dict(comment) + + replies = db.execute(''' + SELECT c.*, u.username, u.avatar, u.is_verified, + (SELECT COUNT(*) FROM comment_likes WHERE comment_id = c.id) as likes_count + FROM comments c + JOIN users u ON c.user_id = u.id + WHERE c.parent_id = ? + ORDER BY c.timestamp ASC + LIMIT 10 + ''', (comment['id'],)).fetchall() + + c['replies'] = [] + for r in replies: + reply = dict(r) + liked = db.execute('SELECT * FROM comment_likes WHERE user_id = ? AND comment_id = ?', + (session['user_id'], r['id'])).fetchone() + reply['liked_by_user'] = liked is not None + c['replies'].append(reply) + liked = db.execute('SELECT * FROM comment_likes WHERE user_id = ? AND comment_id = ?', - (session['user_id'], r['id'])).fetchone() - reply['liked_by_user'] = liked is not None - reply['avatar_url'] = f'/avatars/{r["avatar"]}' - c['replies'].append(reply) + (session['user_id'], comment['id'])).fetchone() + c['liked_by_user'] = liked is not None + result.append(c) - liked = db.execute('SELECT * FROM comment_likes WHERE user_id = ? AND comment_id = ?', - (session['user_id'], comment['id'])).fetchone() - c['liked_by_user'] = liked is not None - c['avatar_url'] = f'/avatars/{comment["avatar"]}' - result.append(c) + return jsonify(result) - return jsonify(result) + except Exception as e: + logger.error(f"Error in get_comments: {e}") + return jsonify([]), 500 - except Exception as e: - logger.error(f"Error in get_comments: {e}") - return jsonify([]), 500 - -@app.route('/api/comment/', methods=['POST']) -@login_required -def add_comment(video_id): - try: - user_id = session['user_id'] - data = request.get_json() - comment_text = data.get('comment', '').strip() - parent_id = data.get('parent_id') - - if not comment_text: - return jsonify({'error': 'التعليق لا يمكن أن يكون فارغاً'}), 400 - - if len(comment_text) > 500: - return jsonify({'error': 'التعليق طويل جداً'}), 400 - - db = get_db() - cursor = db.execute(''' - INSERT INTO comments (user_id, video_id, parent_id, comment_text) - VALUES (?, ?, ?, ?) - ''', (user_id, video_id, parent_id, comment_text)) - - comment_id = cursor.lastrowid - db.execute('UPDATE videos SET comments_count = comments_count + 1 WHERE id = ?', (video_id,)) - - video = db.execute('SELECT user_id FROM videos WHERE id = ?', (video_id,)).fetchone() - if video and video['user_id'] != user_id: - user = db.execute('SELECT username FROM users WHERE id = ?', (user_id,)).fetchone() - if user: - add_notification(video['user_id'], user_id, 'comment', f'علق {user["username"]} على فيديو لك', video_id, comment_id) - - if parent_id: - parent = db.execute('SELECT user_id FROM comments WHERE id = ?', (parent_id,)).fetchone() - if parent and parent['user_id'] != user_id: + else: # POST + try: + user_id = session['user_id'] + data = request.get_json() + comment_text = data.get('comment', '').strip() + parent_id = data.get('parent_id') + + if not comment_text: + return jsonify({'error': 'التعليق لا يمكن أن يكون فارغاً'}), 400 + + if len(comment_text) > 500: + return jsonify({'error': 'التعليق طويل جداً'}), 400 + + db = get_db() + cursor = db.execute(''' + INSERT INTO comments (user_id, video_id, parent_id, comment_text) + VALUES (?, ?, ?, ?) + ''', (user_id, video_id, parent_id, comment_text)) + + comment_id = cursor.lastrowid + db.execute('UPDATE videos SET comments_count = comments_count + 1 WHERE id = ?', (video_id,)) + + video = db.execute('SELECT user_id FROM videos WHERE id = ?', (video_id,)).fetchone() + if video and video['user_id'] != user_id: user = db.execute('SELECT username FROM users WHERE id = ?', (user_id,)).fetchone() if user: - add_notification(parent['user_id'], user_id, 'reply', f'رد {user["username"]} على تعليقك', video_id, comment_id) - - db.commit() - - add_xp(user_id, 'comment', 3) - track_event(user_id, 'add_comment', {'video_id': video_id, 'comment_id': comment_id}) + add_notification(video['user_id'], user_id, 'comment', f'علق {user["username"]} على فيديو لك', video_id, comment_id) + + if parent_id: + parent = db.execute('SELECT user_id FROM comments WHERE id = ?', (parent_id,)).fetchone() + if parent and parent['user_id'] != user_id: + user = db.execute('SELECT username FROM users WHERE id = ?', (user_id,)).fetchone() + if user: + add_notification(parent['user_id'], user_id, 'reply', f'رد {user["username"]} على تعليقك', video_id, comment_id) + + db.commit() + + add_xp(user_id, 'comment', 3) + track_event(user_id, 'add_comment', {'video_id': video_id, 'comment_id': comment_id}) + + return jsonify({'status': 'ok', 'comment_id': comment_id}) - return jsonify({'status': 'ok', 'comment_id': comment_id}) - - except Exception as e: - logger.error(f"Error in add_comment: {e}") - return jsonify({'error': str(e)}), 500 + except Exception as e: + logger.error(f"Error in add_comment: {e}") + return jsonify({'error': str(e)}), 500 @app.route('/api/comment/like/', methods=['POST']) @login_required -def like_comment(comment_id): +def api_comment_like(comment_id): try: user_id = session['user_id'] db = get_db() @@ -2255,212 +1853,201 @@ def like_comment(comment_id): logger.error(f"Error in like_comment: {e}") return jsonify({'error': str(e)}), 500 -@app.route('/api/comment/pin/', methods=['POST']) +# مسارات الإشعارات +@app.route('/api/notifications/stream') @login_required -def pin_comment(comment_id): - try: - user_id = session['user_id'] - db = get_db() - - comment = db.execute(''' - SELECT c.video_id, v.user_id - FROM comments c - JOIN videos v ON c.video_id = v.id - WHERE c.id = ? - ''', (comment_id,)).fetchone() - - if not comment or comment['user_id'] != user_id: - return jsonify({'error': 'غير مصرح'}), 403 - - db.execute('UPDATE comments SET is_pinned = 0 WHERE video_id = ?', (comment['video_id'],)) - db.execute('UPDATE comments SET is_pinned = 1 WHERE id = ?', (comment_id,)) - db.commit() - - track_event(user_id, 'pin_comment', {'comment_id': comment_id}) +def notification_stream(): + user_id = session['user_id'] + + def event_stream(): + q = queue.Queue() + notification_queues[user_id] = q - return jsonify({'status': 'ok'}) + try: + while True: + try: + notification = q.get(timeout=30) + yield f"data: {json.dumps(notification, ensure_ascii=False)}\n\n" + except queue.Empty: + yield "data: {}\n\n".format(json.dumps({'type': 'ping'}, ensure_ascii=False)) + except GeneratorExit: + notification_queues.pop(user_id, None) - except Exception as e: - logger.error(f"Error in pin_comment: {e}") - return jsonify({'error': str(e)}), 500 + return Response(event_stream(), mimetype="text/event-stream", + headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'}) -@app.route('/api/comment/delete/', methods=['POST']) +@app.route('/api/notifications') @login_required -def delete_comment(comment_id): +def get_notifications(): try: - user_id = session['user_id'] db = get_db() - comment = db.execute(''' - SELECT c.user_id, c.video_id, v.user_id as video_owner - FROM comments c - JOIN videos v ON c.video_id = v.id - WHERE c.id = ? - ''', (comment_id,)).fetchone() - - if not comment: - return jsonify({'error': 'التعليق غير موجود'}), 404 - - is_owner = comment['user_id'] == user_id - is_video_owner = comment['video_owner'] == user_id - is_admin = session.get('is_admin', False) - - if not (is_owner or is_video_owner or is_admin): - return jsonify({'error': 'غير مصرح'}), 403 + notifications = db.execute(''' + SELECT n.*, u.username, u.avatar + FROM notifications n + LEFT JOIN users u ON n.from_user_id = u.id + WHERE n.user_id = ? + ORDER BY n.created_at DESC + LIMIT 50 + ''', (session['user_id'],)).fetchall() - db.execute('DELETE FROM comments WHERE id = ? OR parent_id = ?', (comment_id, comment_id)) - db.execute('UPDATE videos SET comments_count = comments_count - 1 WHERE id = ?', (comment['video_id'],)) + db.execute('UPDATE notifications SET is_read = 1 WHERE user_id = ?', (session['user_id'],)) db.commit() - track_event(user_id, 'delete_comment', {'comment_id': comment_id}) - - return jsonify({'status': 'ok'}) - - except Exception as e: - logger.error(f"Error in delete_comment: {e}") - return jsonify({'error': str(e)}), 500 - -# ---------- مسارات الذكاء الاصطناعي ---------- -@app.route('/api/ai/recommend', methods=['GET']) -@login_required -def ai_recommend(): - """توصيات محسنة""" - try: - limit = int(request.args.get('limit', 10)) - videos = recommend_videos(session['user_id'], limit) - result = [] - for v in videos: - video = dict(v) - video['url'] = f'/videos/{v["filename"]}' - video['avatar_url'] = f'/avatars/{v["avatar"]}' - video.pop('vector', None) - result.append(video) + for n in notifications: + notif = dict(n) + notif['avatar_url'] = f"/avatars/{n['avatar']}" if n['avatar'] else '/avatars/default.jpg' + result.append(notif) return jsonify(result) except Exception as e: - logger.error(f"AI recommend error: {e}") - return jsonify({'error': str(e)}), 500 + logger.error(f"Error in get_notifications: {e}") + return jsonify([]), 500 -@app.route('/api/ai/analyze/', methods=['POST']) +@app.route('/api/notifications/count') @login_required -def ai_analyze_video(video_id): - """تحليل فيديو""" +def notification_count(): try: db = get_db() - video = db.execute('SELECT filepath, title, description FROM videos WHERE id = ?', (video_id,)).fetchone() - - if not video: - return jsonify({'error': 'فيديو غير موجود'}), 404 - - nsfw_score = analyze_video_nsfw(video['filepath']) if HAS_CV2 else 0.0 - ai_tags = generate_auto_tags(video['filepath'], video['title'], video['description']) - - db.execute('UPDATE videos SET nsfw_score = ?, ai_tags = ?, processed_for_ai = 1 WHERE id = ?', - (nsfw_score, ai_tags, video_id)) - db.commit() - - track_event(session['user_id'], 'ai_analyze', {'video_id': video_id}) + count = db.execute('SELECT COUNT(*) FROM notifications WHERE user_id = ? AND is_read = 0', + (session['user_id'],)).fetchone()[0] - return jsonify({'nsfw_score': nsfw_score, 'ai_tags': json.loads(ai_tags)}) + return jsonify({'count': count}) except Exception as e: - logger.error(f"AI analyze error: {e}") - return jsonify({'error': str(e)}), 500 + logger.error(f"Error in notification_count: {e}") + return jsonify({'count': 0}), 500 -@app.route('/api/ai/generate_tags', methods=['POST']) +# مسارات التحليلات +@app.route('/api/analytics/stats', methods=['GET']) @login_required -def ai_generate_tags(): - """توليد وسوم تلقائية لنص""" - try: - data = request.get_json() - text = data.get('text', '') - - words = re.findall(r'\w+', text) - tags = list(set([w for w in words if len(w) > 2]))[:15] - - return jsonify({'tags': tags}) +def analytics_stats(): + stats = get_user_stats(session['user_id']) + return jsonify(stats) + +@app.route('/api/analytics/track', methods=['POST']) +@login_required +def track_analytics(): + data = request.get_json() + event_name = data.get('event_name') + event_data = data.get('event_data') - except Exception as e: - logger.error(f"AI generate tags error: {e}") - return jsonify({'error': str(e)}), 500 + if not event_name: + return jsonify({'error': 'event_name مطلوب'}), 400 + + track_event(session['user_id'], event_name, event_data) + return jsonify({'status': 'ok'}) + +# مسارات التسويق بالعمولة +@app.route('/api/affiliate/link', methods=['GET']) +@login_required +def get_affiliate_link(): + link = generate_affiliate_link(session['user_id']) + return jsonify({'link': request.host_url.rstrip('/') + link}) + +@app.route('/api/affiliate/stats', methods=['GET']) +@login_required +def affiliate_stats(): + db = get_db() + user = db.execute(''' + SELECT affiliate_clicks, affiliate_conversions, affiliate_balance + FROM users WHERE id = ? + ''', (session['user_id'],)).fetchone() + + clicks = db.execute(''' + SELECT clicked_at, converted FROM affiliate_clicks + WHERE referrer_id = ? ORDER BY clicked_at DESC LIMIT 50 + ''', (session['user_id'],)).fetchall() + + return jsonify({ + 'clicks': user['affiliate_clicks'], + 'conversions': user['affiliate_conversions'], + 'balance': user['affiliate_balance'], + 'recent_clicks': [dict(c) for c in clicks] + }) + +@app.route('/r/') +def referral_redirect(referral_code): + ip = request.remote_addr + user_agent = request.headers.get('User-Agent', '') + track_affiliate_click(referral_code, ip, user_agent) + return redirect(url_for('register', ref=referral_code)) -# ---------- مسارات التشفير ---------- -@app.route('/api/security/encrypt/', methods=['POST']) +# مسارات العملات والمكافآت +@app.route('/api/coins/balance') @login_required -def encrypt_video_route(video_id): - """تشفير فيديو""" +def coins_balance(): try: db = get_db() - video = db.execute('SELECT filepath, encrypted_path FROM videos WHERE id = ? AND user_id = ?', - (video_id, session['user_id'])).fetchone() - - if not video: - return jsonify({'error': 'فيديو غير موجود أو لا تملك صلاحيات'}), 404 - - if video['encrypted_path']: - return jsonify({'error': 'الفيديو مشفر بالفعل'}), 400 - - enc_path, key = encrypt_video_file(video['filepath']) - - if not enc_path: - return jsonify({'error': 'فشل التشفير'}), 500 - - db.execute('UPDATE videos SET encrypted_path = ?, encrypted_key = ? WHERE id = ?', - (enc_path, key, video_id)) - db.commit() - - track_event(session['user_id'], 'encrypt_video', {'video_id': video_id}) - - return jsonify({'status': 'ok', 'encrypted_path': enc_path}) + user = db.execute('SELECT coins, diamonds FROM users WHERE id = ?', (session['user_id'],)).fetchone() + return jsonify({'coins': user['coins'], 'diamonds': user['diamonds']}) except Exception as e: - logger.error(f"Encrypt error: {e}") - return jsonify({'error': str(e)}), 500 + logger.error(f"Error in coins_balance: {e}") + return jsonify({'coins': 0, 'diamonds': 0}), 500 -@app.route('/api/security/watermark/', methods=['POST']) +@app.route('/api/transactions') @login_required -def watermark_video_route(video_id): - """إضافة علامة مائية""" +def get_transactions(): try: - data = request.get_json() or {} - text = data.get('text', 'ARC') - db = get_db() - video = db.execute('SELECT filepath, watermarked_path FROM videos WHERE id = ? AND user_id = ?', - (video_id, session['user_id'])).fetchone() - - if not video: - return jsonify({'error': 'فيديو غير موجود أو لا تملك صلاحيات'}), 404 - - wm_path = add_watermark_to_video(video['filepath'], watermark_text=text) - - if not wm_path: - return jsonify({'error': 'فشل إضافة العلامة المائية'}), 500 - - db.execute('UPDATE videos SET watermarked_path = ? WHERE id = ?', (wm_path, video_id)) - db.commit() - - track_event(session['user_id'], 'watermark_video', {'video_id': video_id}) + transactions = db.execute(''' + SELECT * FROM transactions + WHERE user_id = ? + ORDER BY created_at DESC + LIMIT 100 + ''', (session['user_id'],)).fetchall() - return jsonify({'status': 'ok', 'watermarked_path': wm_path}) + return jsonify([dict(t) for t in transactions]) except Exception as e: - logger.error(f"Watermark error: {e}") - return jsonify({'error': str(e)}), 500 - -# ---------- مسارات المطور ---------- + logger.error(f"Error in get_transactions: {e}") + return jsonify([]), 500 + +@app.route('/api/gamification/daily', methods=['POST']) +@login_required +def claim_daily(): + success, *rest = claim_daily_reward(session['user_id']) + + if success: + coins, streak = rest + track_event(session['user_id'], 'claim_daily', {'coins': coins, 'streak': streak}) + return jsonify({'status': 'ok', 'coins': coins, 'streak': streak}) + else: + return jsonify({'error': rest[0]}), 400 + +@app.route('/api/gamification/leaderboard', methods=['GET']) +@login_required +def leaderboard(): + limit = int(request.args.get('limit', 50)) + + db = get_db() + users = db.execute(''' + SELECT username, avatar, xp, level, is_verified + FROM users + ORDER BY xp DESC + LIMIT ? + ''', (limit,)).fetchall() + + result = [] + for u in users: + user = dict(u) + user['avatar_url'] = f"/avatars/{u['avatar']}" + result.append(user) + + return jsonify(result) + +# مسارات المطور @app.route('/developer') @developer_required def developer_panel(): - """لوحة تحكم المطور""" return render_template_string(DEVELOPER_TEMPLATE) @app.route('/api/developer/session/create', methods=['POST']) @login_required def create_dev_session(): - """إنشاء جلسة مطور""" if not session.get('is_developer'): return jsonify({'error': 'غير مصرح'}), 403 @@ -2470,21 +2057,18 @@ def create_dev_session(): @app.route('/api/developer/execute', methods=['POST']) @login_required def execute_code_route(): - """تنفيذ كود Python""" if not session.get('is_developer'): return jsonify({'error': 'غير مصرح'}), 403 data = request.get_json() code = data.get('code', '') - # التحقق من جلسة المطور token = request.headers.get('X-Dev-Token') if token: uid = validate_developer_session(token) if uid != session['user_id']: return jsonify({'error': 'جلسة مطور غير صالحة'}), 401 - # تنفيذ الكود result = execute_code_in_sandbox(code) track_event(session['user_id'], 'execute_code', {'code_length': len(code)}) @@ -2494,7 +2078,6 @@ def execute_code_route(): @app.route('/api/developer/plugins', methods=['GET']) @developer_required def list_plugins(): - """قائمة الإضافات""" db = get_db() plugins = db.execute('SELECT id, name, is_active, created_at FROM developer_plugins WHERE user_id = ?', (session['user_id'],)).fetchall() @@ -2504,7 +2087,6 @@ def list_plugins(): @app.route('/api/developer/plugins/upload', methods=['POST']) @developer_required def upload_plugin_route(): - """رفع إضافة جديدة""" if 'plugin' not in request.files: return jsonify({'error': 'لم يتم اختيار ملف'}), 400 @@ -2532,7 +2114,6 @@ def upload_plugin_route(): @app.route('/api/developer/plugins/toggle/', methods=['POST']) @developer_required def toggle_plugin_route(plugin_id): - """تفعيل/تعطيل إضافة""" data = request.get_json() is_active = data.get('is_active', True) @@ -2541,1583 +2122,232 @@ def toggle_plugin_route(plugin_id): (is_active, plugin_id, session['user_id'])) db.commit() - # إعادة تحميل الإضافات load_all_plugins() track_event(session['user_id'], 'toggle_plugin', {'plugin_id': plugin_id, 'is_active': is_active}) return jsonify({'status': 'ok'}) -# ---------- مسارات الألعاب ---------- -@app.route('/api/gamification/xp', methods=['GET']) -@login_required -def get_xp(): - """الحصول على نقاط الخبرة""" - db = get_db() - user = db.execute('SELECT xp, level FROM users WHERE id = ?', (session['user_id'],)).fetchone() - - history = db.execute(''' - SELECT action, xp_gained, created_at FROM user_xp - WHERE user_id = ? ORDER BY created_at DESC LIMIT 20 - ''', (session['user_id'],)).fetchall() - - return jsonify({ - 'xp': user['xp'], - 'level': user['level'], - 'history': [dict(h) for h in history] - }) - -@app.route('/api/gamification/daily', methods=['POST']) -@login_required -def claim_daily(): - """المطالبة بالمكافأة اليومية""" - success, *rest = claim_daily_reward(session['user_id']) - - if success: - coins, streak = rest - track_event(session['user_id'], 'claim_daily', {'coins': coins, 'streak': streak}) - return jsonify({'status': 'ok', 'coins': coins, 'streak': streak}) - else: - return jsonify({'error': rest[0]}), 400 - -@app.route('/api/gamification/leaderboard', methods=['GET']) -@login_required -def leaderboard(): - """لوحة المتصدرين""" - limit = int(request.args.get('limit', 50)) - - db = get_db() - users = db.execute(''' - SELECT username, avatar, xp, level, is_verified - FROM users - ORDER BY xp DESC - LIMIT ? - ''', (limit,)).fetchall() - - result = [] - for u in users: - user = dict(u) - user['avatar_url'] = f'/avatars/{u["avatar"]}' - result.append(user) - - return jsonify(result) - -# ---------- مسارات التحليلات ---------- -@app.route('/api/analytics/stats', methods=['GET']) -@login_required -def analytics_stats(): - """إحصائيات المستخدم""" - stats = get_user_stats(session['user_id']) - return jsonify(stats) - -@app.route('/api/analytics/track', methods=['POST']) -@login_required -def track_analytics(): - """تتبع حدث مخصص""" - data = request.get_json() - event_name = data.get('event_name') - event_data = data.get('event_data') - - if not event_name: - return jsonify({'error': 'event_name مطلوب'}), 400 - - track_event(session['user_id'], event_name, event_data) - return jsonify({'status': 'ok'}) +# مسارات الملفات الثابتة +@app.route('/avatars/') +def serve_avatar(filename): + return send_from_directory(AVATAR_FOLDER, filename) -# ---------- مسارات التسويق بالعمولة ---------- -@app.route('/api/affiliate/link', methods=['GET']) -@login_required -def get_affiliate_link(): - """الحصول على رابط الإحالة""" - link = generate_affiliate_link(session['user_id']) - return jsonify({'link': request.host_url.rstrip('/') + link}) +@app.route('/thumbnails/') +def serve_thumbnail(filename): + return send_from_directory(THUMBNAIL_FOLDER, filename) -@app.route('/api/affiliate/stats', methods=['GET']) -@login_required -def affiliate_stats(): - """إحصائيات الإحال��ت""" - db = get_db() - user = db.execute(''' - SELECT affiliate_clicks, affiliate_conversions, affiliate_balance - FROM users WHERE id = ? - ''', (session['user_id'],)).fetchone() - - clicks = db.execute(''' - SELECT clicked_at, converted FROM affiliate_clicks - WHERE referrer_id = ? ORDER BY clicked_at DESC LIMIT 50 - ''', (session['user_id'],)).fetchall() - - return jsonify({ - 'clicks': user['affiliate_clicks'], - 'conversions': user['affiliate_conversions'], - 'balance': user['affiliate_balance'], - 'recent_clicks': [dict(c) for c in clicks] - }) +# قوالب HTML +LOGIN_PAGE = ''' + + + + + + تسجيل الدخول - ARC + + + + + + + +''' -@app.route('/r/') -def referral_redirect(referral_code): - """تسجيل نقرة وإعادة توجيه للتسجيل""" - ip = request.remote_addr - user_agent = request.headers.get('User-Agent', '') - track_affiliate_click(referral_code, ip, user_agent) - return redirect(url_for('register', ref=referral_code)) - -# ---------- نظام الإشعارات ---------- -@app.route('/api/notifications/stream') -@login_required -def notification_stream(): - """بث الإشعارات المباشر""" - user_id = session['user_id'] - - def event_stream(): - q = queue.Queue() - notification_queues[user_id] = q - - try: - while True: - try: - notification = q.get(timeout=30) - yield f"data: {json.dumps(notification, ensure_ascii=False)}\n\n" - except queue.Empty: - yield "data: {}\n\n".format(json.dumps({'type': 'ping'}, ensure_ascii=False)) - except GeneratorExit: - notification_queues.pop(user_id, None) - - return Response(event_stream(), mimetype="text/event-stream", - headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'}) - -@app.route('/api/notifications') -@login_required -def get_notifications(): - """الحصول على الإشعارات""" - try: - db = get_db() - - notifications = db.execute(''' - SELECT n.*, u.username, u.avatar - FROM notifications n - LEFT JOIN users u ON n.from_user_id = u.id - WHERE n.user_id = ? - ORDER BY n.created_at DESC - LIMIT 50 - ''', (session['user_id'],)).fetchall() - - db.execute('UPDATE notifications SET is_read = 1 WHERE user_id = ?', (session['user_id'],)) - db.commit() - - result = [] - for n in notifications: - notif = dict(n) - notif['avatar_url'] = f'/avatars/{n["avatar"]}' if n['avatar'] else '/avatars/default.jpg' - result.append(notif) - - return jsonify(result) - - except Exception as e: - logger.error(f"Error in get_notifications: {e}") - return jsonify([]), 500 - -@app.route('/api/notifications/count') -@login_required -def notification_count(): - """عدد الإشعارات غير المقروءة""" - try: - db = get_db() - count = db.execute('SELECT COUNT(*) FROM notifications WHERE user_id = ? AND is_read = 0', - (session['user_id'],)).fetchone()[0] - - return jsonify({'count': count}) - - except Exception as e: - logger.error(f"Error in notification_count: {e}") - return jsonify({'count': 0}), 500 - -# ---------- نظام الرسائل الخاصة ---------- -@app.route('/api/messages/') -@login_required -def get_messages(user_id): - """الحصول على المحادثة مع مستخدم""" - try: - db = get_db() - - messages = db.execute(''' - SELECT m.*, u_sender.username as sender_name, u_sender.avatar as sender_avatar - FROM messages m - JOIN users u_sender ON m.sender_id = u_sender.id - WHERE (m.sender_id = ? AND m.receiver_id = ?) OR (m.sender_id = ? AND m.receiver_id = ?) - ORDER BY m.created_at ASC - LIMIT 100 - ''', (session['user_id'], user_id, user_id, session['user_id'])).fetchall() - - db.execute(''' - UPDATE messages SET is_read = 1 - WHERE sender_id = ? AND receiver_id = ? AND is_read = 0 - ''', (user_id, session['user_id'])) - db.commit() - - result = [] - for m in messages: - msg = dict(m) - msg['sender_avatar_url'] = f'/avatars/{m["sender_avatar"]}' - result.append(msg) - - return jsonify(result) - - except Exception as e: - logger.error(f"Error in get_messages: {e}") - return jsonify([]), 500 - -@app.route('/api/messages/send/', methods=['POST']) -@login_required -def send_message_route(receiver_id): - """إرسال رسالة""" - try: - data = request.get_json() - message = data.get('message', '').strip() - - if not message: - return jsonify({'error': 'الرسالة لا يمكن أن تكون فارغة'}), 400 - - if len(message) > 1000: - return jsonify({'error': 'الرسالة طويلة جداً'}), 400 - - db = get_db() - cursor = db.execute(''' - INSERT INTO messages (sender_id, receiver_id, message) - VALUES (?, ?, ?) - ''', (session['user_id'], receiver_id, message)) - - db.commit() - - sender = db.execute('SELECT username FROM users WHERE id = ?', (session['user_id'],)).fetchone() - if sender: - add_notification(receiver_id, session['user_id'], 'message', f'رسالة جديدة من {sender["username"]}') - - track_event(session['user_id'], 'send_message', {'receiver_id': receiver_id}) - - return jsonify({'status': 'ok', 'message_id': cursor.lastrowid}) - - except Exception as e: - logger.error(f"Error in send_message: {e}") - return jsonify({'error': str(e)}), 500 - -@app.route('/api/conversations') -@login_required -def get_conversations(): - """الحصول على قائمة المحادثات""" - try: - db = get_db() - - conversations = db.execute(''' - SELECT DISTINCT - CASE - WHEN m.sender_id = ? THEN m.receiver_id - ELSE m.sender_id - END as other_user_id, - u.username, - u.avatar, - u.is_verified, - (SELECT message FROM messages - WHERE (sender_id = ? AND receiver_id = other_user_id) - OR (sender_id = other_user_id AND receiver_id = ?) - ORDER BY created_at DESC LIMIT 1) as last_message, - (SELECT created_at FROM messages - WHERE (sender_id = ? AND receiver_id = other_user_id) - OR (sender_id = other_user_id AND receiver_id = ?) - ORDER BY created_at DESC LIMIT 1) as last_time, - (SELECT COUNT(*) FROM messages - WHERE sender_id = other_user_id AND receiver_id = ? AND is_read = 0) as unread_count - FROM messages m - JOIN users u ON u.id = CASE WHEN m.sender_id = ? THEN m.receiver_id ELSE m.sender_id END - WHERE m.sender_id = ? OR m.receiver_id = ? - GROUP BY other_user_id - ORDER BY last_time DESC - ''', (session['user_id'], session['user_id'], session['user_id'], - session['user_id'], session['user_id'], session['user_id'], - session['user_id'], session['user_id'])).fetchall() - - result = [] - for conv in conversations: - c = dict(conv) - c['avatar_url'] = f'/avatars/{conv["avatar"]}' - result.append(c) - - return jsonify(result) - - except Exception as e: - logger.error(f"Error in get_conversations: {e}") - return jsonify([]), 500 - -# ---------- نظام البث المباشر ---------- -@app.route('/live') -@login_required -def live_page(): - """صفحة البث المباشر""" - return render_template_string(LIVE_TEMPLATE) - -@app.route('/api/live/start', methods=['POST']) -@login_required -def start_live(): - """بدء بث مباشر""" - try: - data = request.get_json() - title = data.get('title', 'بث مباشر جديد') - - db = get_db() - stream_key = hashlib.md5(f"{session['user_id']}_{uuid.uuid4()}".encode()).hexdigest() - - # إنهاء أي بث سابق - db.execute('UPDATE live_streams SET is_active = 0, ended_at = CURRENT_TIMESTAMP WHERE user_id = ? AND is_active = 1', - (session['user_id'],)) - - db.execute(''' - INSERT INTO live_streams (user_id, stream_key, title) - VALUES (?, ?, ?) - ''', (session['user_id'], stream_key, title)) - - stream_id = db.execute("SELECT last_insert_rowid()").fetchone()[0] - - db.execute('UPDATE users SET is_live = 1 WHERE id = ?', (session['user_id'],)) - db.commit() - - # تخزين في الذاكرة - active_streams[stream_id] = { - 'user_id': session['user_id'], - 'viewers': 0, - 'started_at': datetime.now() - } - - track_event(session['user_id'], 'start_live', {'stream_id': stream_id, 'title': title}) - - return jsonify({ - 'status': 'ok', - 'stream_id': stream_id, - 'stream_key': stream_key, - 'rtmp_url': 'rtmp://your-server.com/live' - }) - - except Exception as e: - logger.error(f"Error in start_live: {e}") - return jsonify({'error': str(e)}), 500 - -@app.route('/api/live/stop', methods=['POST']) -@login_required -def stop_live(): - """إنهاء بث مباشر""" - try: - db = get_db() - - stream = db.execute('SELECT id FROM live_streams WHERE user_id = ? AND is_active = 1', - (session['user_id'],)).fetchone() - - if stream: - db.execute('UPDATE live_streams SET is_active = 0, ended_at = CURRENT_TIMESTAMP WHERE id = ?', - (stream['id'],)) - - if stream['id'] in active_streams: - del active_streams[stream['id']] - - db.execute('UPDATE users SET is_live = 0 WHERE id = ?', (session['user_id'],)) - db.commit() - - track_event(session['user_id'], 'stop_live', {}) - - return jsonify({'status': 'ok'}) - - except Exception as e: - logger.error(f"Error in stop_live: {e}") - return jsonify({'error': str(e)}), 500 - -@app.route('/api/live/active') -@login_required -def get_active_streams(): - """الحصول على البثوث النشطة""" - try: - db = get_db() - - streams = db.execute(''' - SELECT ls.*, u.username, u.avatar - FROM live_streams ls - JOIN users u ON ls.user_id = u.id - WHERE ls.is_active = 1 - ORDER BY ls.viewers DESC - LIMIT 50 - ''').fetchall() - - result = [] - for s in streams: - stream = dict(s) - stream['avatar_url'] = f'/avatars/{s["avatar"]}' - result.append(stream) - - return jsonify(result) - - except Exception as e: - logger.error(f"Error in get_active_streams: {e}") - return jsonify([]), 500 - -@app.route('/api/live//view', methods=['POST']) -@login_required -def view_stream(stream_id): - """تسجيل مشاهدة بث""" - try: - db = get_db() - db.execute('UPDATE live_streams SET viewers = viewers + 1 WHERE id = ?', (stream_id,)) - db.commit() - - if stream_id in active_streams: - active_streams[stream_id]['viewers'] += 1 - - return jsonify({'status': 'ok'}) - - except Exception as e: - logger.error(f"Error in view_stream: {e}") - return jsonify({'error': str(e)}), 500 - -@app.route('/api/live/gift/', methods=['POST']) -@login_required -def send_gift(stream_id): - """إرسال هدية في البث""" - try: - data = request.get_json() - gift_type = data.get('gift_type', 'rose') - - gift_prices = {'rose': 10, 'crown': 100, 'diamond': 500, 'car': 1000, 'house': 5000} - price = gift_prices.get(gift_type, 10) - - db = get_db() - user = db.execute('SELECT coins FROM users WHERE id = ?', (session['user_id'],)).fetchone() - - if user['coins'] < price: - return jsonify({'error': 'رصيد غير كافٍ'}), 400 - - if deduct_coins(session['user_id'], price, f'إرسال هدية {gift_type}'): - db.execute(''' - INSERT INTO live_gifts (stream_id, user_id, gift_type, gift_value) - VALUES (?, ?, ?, ?) - ''', (stream_id, session['user_id'], gift_type, price)) - - stream = db.execute('SELECT user_id FROM live_streams WHERE id = ?', (stream_id,)).fetchone() - if stream: - add_coins(stream['user_id'], price // 2, f'هدية {gift_type} من {session["username"]}') - - db.commit() - - track_event(session['user_id'], 'send_gift', - {'stream_id': stream_id, 'gift_type': gift_type, 'price': price}) - - return jsonify({'status': 'ok'}) - else: - return jsonify({'error': 'فشل في خصم العملات'}), 400 - - except Exception as e: - logger.error(f"Error in send_gift: {e}") - return jsonify({'error': str(e)}), 500 - -# ---------- نظام العملات والمكافآت ---------- -@app.route('/api/coins/balance') -@login_required -def coins_balance(): - """رصيد العملات""" - try: - db = get_db() - user = db.execute('SELECT coins, diamonds FROM users WHERE id = ?', (session['user_id'],)).fetchone() - return jsonify({'coins': user['coins'], 'diamonds': user['diamonds']}) - - except Exception as e: - logger.error(f"Error in coins_balance: {e}") - return jsonify({'coins': 0, 'diamonds': 0}), 500 - -@app.route('/api/transactions') -@login_required -def get_transactions(): - """سجل المعاملات""" - try: - db = get_db() - transactions = db.execute(''' - SELECT * FROM transactions - WHERE user_id = ? - ORDER BY created_at DESC - LIMIT 100 - ''', (session['user_id'],)).fetchall() - - return jsonify([dict(t) for t in transactions]) - - except Exception as e: - logger.error(f"Error in get_transactions: {e}") - return jsonify([]), 500 - -@app.route('/api/coins/redeem', methods=['POST']) -@login_required -def redeem_coins(): - """استبدال العملات""" - try: - data = request.get_json() - amount = data.get('amount', 0) - - if amount < 1000: - return jsonify({'error': 'الحد الأدنى للاستبدال 1000 عملة'}), 400 - - db = get_db() - user = db.execute('SELECT coins FROM users WHERE id = ?', (session['user_id'],)).fetchone() - - if user['coins'] < amount: - return jsonify({'error': 'رصيد غير كافٍ'}), 400 - - if deduct_coins(session['user_id'], amount, f'استبدال {amount} عملة'): - track_event(session['user_id'], 'redeem_coins', {'amount': amount}) - return jsonify({'status': 'ok', 'message': 'تم استبدال العملات بنجاح'}) - else: - return jsonify({'error': 'فشل في استبدال العملات'}), 400 - - except Exception as e: - logger.error(f"Error in redeem_coins: {e}") - return jsonify({'error': str(e)}), 500 - -# ---------- نظام البلاغات ---------- -@app.route('/api/report', methods=['POST']) -@login_required -def create_report(): - """إنشاء بلاغ""" - try: - data = request.get_json() - report_type = data.get('type') - reported_id = data.get('id') - reason = data.get('reason') - - if not reason: - return jsonify({'error': 'يرجى ذكر سبب البلاغ'}), 400 - - if len(reason) < 10: - return jsonify({'error': 'الرجاء كتابة سبب أكثر تفصيلاً'}), 400 - - db = get_db() - - if report_type == 'user': - db.execute(''' - INSERT INTO reports (reporter_id, reported_user_id, reason) - VALUES (?, ?, ?) - ''', (session['user_id'], reported_id, reason)) - - elif report_type == 'video': - db.execute(''' - INSERT INTO reports (reporter_id, reported_video_id, reason) - VALUES (?, ?, ?) - ''', (session['user_id'], reported_id, reason)) - - db.execute('UPDATE videos SET report_count = report_count + 1 WHERE id = ?', (reported_id,)) - - video = db.execute('SELECT report_count FROM videos WHERE id = ?', (reported_id,)).fetchone() - if video and video['report_count'] >= 5: - db.execute('UPDATE videos SET is_reported = 1 WHERE id = ?', (reported_id,)) - - elif report_type == 'comment': - db.execute(''' - INSERT INTO reports (reporter_id, reported_comment_id, reason) - VALUES (?, ?, ?) - ''', (session['user_id'], reported_id, reason)) - - db.execute('UPDATE comments SET report_count = report_count + 1 WHERE id = ?', (reported_id,)) - - comment = db.execute('SELECT report_count FROM comments WHERE id = ?', (reported_id,)).fetchone() - if comment and comment['report_count'] >= 3: - db.execute('UPDATE comments SET is_reported = 1 WHERE id = ?', (reported_id,)) - - else: - return jsonify({'error': 'نوع البلاغ غير صحيح'}), 400 - - db.commit() - - add_coins(session['user_id'], 5, 'مكافأة الإبلاغ عن محتوى غير لائق') - track_event(session['user_id'], 'create_report', {'type': report_type, 'id': reported_id}) - - return jsonify({'status': 'ok'}) - - except Exception as e: - logger.error(f"Error in create_report: {e}") - return jsonify({'error': str(e)}), 500 - -# ---------- لوحة الإدارة ---------- -@app.route('/admin') -@admin_required -def admin_panel(): - """لوحة تحكم المشرف""" - db = get_db() - - stats = { - 'users': db.execute('SELECT COUNT(*) FROM users').fetchone()[0], - 'videos': db.execute('SELECT COUNT(*) FROM videos').fetchone()[0], - 'comments': db.execute('SELECT COUNT(*) FROM comments').fetchone()[0], - 'reports': db.execute('SELECT COUNT(*) FROM reports WHERE status = "pending"').fetchone()[0] - } - - reports = db.execute(''' - SELECT r.*, u_reporter.username as reporter_name, - u_reported.username as reported_name, - v.filename as video_name, - c.comment_text - FROM reports r - LEFT JOIN users u_reporter ON r.reporter_id = u_reporter.id - LEFT JOIN users u_reported ON r.reported_user_id = u_reported.id - LEFT JOIN videos v ON r.reported_video_id = v.id - LEFT JOIN comments c ON r.reported_comment_id = c.id - WHERE r.status = "pending" - ORDER BY r.created_at DESC - ''').fetchall() - - return render_template_string(ADMIN_TEMPLATE, stats=stats, reports=[dict(r) for r in reports]) - -@app.route('/admin/report//', methods=['POST']) -@admin_required -def handle_report_route(report_id, action): - """معالجة البلاغ""" - try: - db = get_db() - report = db.execute('SELECT * FROM reports WHERE id = ?', (report_id,)).fetchone() - - if not report: - return redirect(url_for('admin_panel')) - - if action == 'accept': - if report['reported_user_id']: - db.execute('UPDATE users SET is_verified = 0 WHERE id = ?', (report['reported_user_id'],)) - - elif report['reported_video_id']: - video = db.execute('SELECT filepath FROM videos WHERE id = ?', (report['reported_video_id'],)).fetchone() - if video: - try: - os.remove(video['filepath']) - except: - pass - db.execute('DELETE FROM videos WHERE id = ?', (report['reported_video_id'],)) - - elif report['reported_comment_id']: - db.execute('DELETE FROM comments WHERE id = ?', (report['reported_comment_id'],)) - - db.execute('UPDATE reports SET status = ? WHERE id = ?', - ('accepted' if action == 'accept' else 'rejected', report_id)) - db.commit() - - return redirect(url_for('admin_panel')) - - except Exception as e: - logger.error(f"Error in handle_report: {e}") - return redirect(url_for('admin_panel')) - -# ---------- دوال الملفات ---------- -@app.route('/videos/') -def serve_video(filename): - """تقديم ملفات الفيديو""" - return send_from_directory(app.config['UPLOAD_FOLDER'], filename) - -@app.route('/avatars/') -def serve_avatar(filename): - """تقديم الصور الشخصية""" - return send_from_directory(app.config['AVATAR_FOLDER'], filename) - -@app.route('/thumbnails/') -def serve_thumbnail(filename): - """تقديم الصور المصغرة""" - return send_from_directory(app.config['THUMBNAIL_FOLDER'], filename) - -@app.route('/encrypted/') -@login_required -def serve_encrypted(filename): - """تقديم الفيديوهات المشفرة""" - return send_from_directory(app.config['ENCRYPTED_FOLDER'], filename) - -@app.route('/watermarked/') -def serve_watermarked(filename): - """تقديم الفيديوهات ذات العلامة المائية""" - return send_from_directory(app.config['WATERMARK_FOLDER'], filename) - -# ---------- مسارات إضافية ---------- -@app.route('/search') -@login_required -def search_page(): - """صفحة البحث""" - return render_template_string(SEARCH_TEMPLATE) - -# ---------- قوالب HTML ---------- -LOGIN_PAGE = ''' - - - - - - تسجيل الدخول - ARC - - - - - - -''' - -REGISTER_PAGE = ''' - - - - - - تسجيل جديد - ARC - - - -
-

ARC

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
- -
- {% if error %}
{{ error }}
{% endif %} - -
- - -''' - -FORGOT_PAGE = ''' - - - - - - استعادة كلمة المرور - ARC - - - -
-

استعادة كلمة المرور

-

أدخل بريدك الإلكتروني وسنرسل لك رابط إعادة التعيين

-
- - -
- {% if message %}
{{ message }}
{% endif %} - -
- - -''' - -UPLOAD_TEMPLATE = ''' - - - - - - رفع فيديو - ARC - - - -
-

رفع فيديو جديد

- -
- -
- - - -
- -
- -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
-
- السماح بالتعليقات -
-
- السماح بالثنائي -
-
- السماح بالمونتاج -
-
- -
- - -
- - -
- العودة للرئيسية -
- - - - -''' - -PROFILE_TEMPLATE = ''' +REGISTER_PAGE = ''' - @{{ user.username }} - ARC + تسجيل جديد - ARC - - - ← العودة للرئيسية - -
- -
- - -
- @{{ user.username }} - {% if user.is_verified %} - ✔ متحقق - {% endif %} -
- -
{{ user.bio or 'لا توجد سيرة ذاتية' }}
- -
-
{{ videos|length }} فيديو
-
{{ followers_count }} متابع
-
{{ following_count }} متابعة
-
{{ total_views }} مشاهدة
-
{{ total_likes }} إعجاب
-
- -
- {% if session.user_id == user.id %} - ⚙️ الإعدادات - {% else %} - - - {% endif %} -
-
- -

📹 فيديوهات @{{ user.username }}

- -
- {% for video in videos %} -
- -
-
{{ video.title or 'بلا عنوان' }}
-
❤️ {{ video.likes_count }} 👁️ {{ video.views }}
-
-
- {% endfor %} -
- - - - -''' - -SETTINGS_TEMPLATE = ''' - - - - - - الإعدادات - ARC - -
-

الإعدادات

- -
- - - -
- - +
+

ARC

+ +
+ +
- -
-

المعلومات الشخصية

- - - - - - - - - - - - - - +
+ +
- -
-

إعدادات الخصوصية

- - - - - +
+ +
- -
-

الإشعارات

-
- - -
-
- - -
-
- - -
-
- - -
+
+ +
- -
-

التفضيلات

-
- - -
- - +
+
- - + - العودة للرئيسية +
- ''' -VIDEO_PAGE_TEMPLATE = ''' +FORGOT_PAGE = ''' - ARC - فيديو + استعادة كلمة المرور - ARC - ← العودة -
- +
+

استعادة كلمة المرور

+

أدخل بريدك الإلكتروني وسنرسل لك رابط إعادة التعيين

+
+ + +
+ {% if message %}
{{ message }}
{% endif %} +
''' -LIVE_TEMPLATE = ''' +DEVELOPER_TEMPLATE = ''' - البث المباشر - ARC + وضع المطور - ARC - - -
- -
-
-

💬 التعليقات

- -
-
-
- - -
-
- - - - - - -''' - -SEARCH_TEMPLATE = ''' - - - - - - بحث - ARC - - - - ← العودة للرئيسية -
-
- - -
-
- -
- - - - -''' - -ADMIN_TEMPLATE = ''' - - - - - - لوحة الإدارة - ARC - - ← العودة للرئيسية -
-

لوحة الإدارة

- -
-

المستخدمين

{{ stats.users }}
-

الفيديوهات

{{ stats.videos }}
-

التعليقات

{{ stats.comments }}
-

البلاغات

{{ stats.reports }}
+
+ + - - -''' -DEVELOPER_TEMPLATE = ''' - - - - - - وضع المطور - ARC - - - - ← العودة للرئيسية -
-

👨‍💻 وضع المطور

- -
-

تنفيذ كود Python

- - - -
النتيجة ستظهر هنا
-
- -
-

إدارة الإضافات

-
- - - -
-
-
-
- - ''' -# ---------- تشغيل التطبيق ---------- -# ---------- تشغيل التطبيق المصلح لبيئة Hugging Face ---------- +# تشغيل التطبيق if __name__ == '__main__': - # حل مشكلة الـ Context عبر إنشاء بيئة مؤقتة للتهيئة with app.app_context(): try: init_db() load_all_plugins() - print("✅ تم تهيئة قاعدة البيانات والإضافات بنجاح داخل الـ Context") + print("✅ تم تهيئة قاعدة البيانات والإضافات بنجاح") except Exception as e: print(f"❌ خطأ أثناء التهيئة: {e}") - - print("=" * 60) - print("🚀 ARC Video - النسخة العالمية على Hugging Face") - print("=" * 60) - # Hugging Face يحتاج بورت 7860 و host 0.0.0.0 - app.run(host='0.0.0.0', port=7860) + app.run(host='0.0.0.0', port=7860) \ No newline at end of file