diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,69 +1,118 @@ import os +import sys import uuid import re import sqlite3 import numpy as np from datetime import datetime, timedelta import random +import string import hashlib import json +import time +import threading +import queue import logging import secrets +import base64 from functools import wraps +from contextlib import contextmanager -# Cloudinary +# استيراد Cloudinary import cloudinary import cloudinary.uploader import cloudinary.api -# Flask -from flask import ( - Flask, request, session, g, jsonify, render_template_string, - redirect, url_for, abort, send_from_directory +# إنشاء المجلدات الضرورية قبل أي شيء +os.makedirs('logs', exist_ok=True) +os.makedirs('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, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) -from werkzeug.utils import secure_filename -from werkzeug.security import generate_password_hash, check_password_hash +logger = logging.getLogger(__name__) -# Optional libraries +# محاولة استيراد المكتبات مع fallback try: - from PIL import Image, ImageDraw + from PIL import Image, ImageDraw, ImageFont HAS_PIL = True except ImportError: HAS_PIL = False + logger.warning("PIL غير مثبت، ميزات الصور محدودة.") try: import cv2 - import numpy as np HAS_CV2 = True except ImportError: HAS_CV2 = False + logger.warning("OpenCV غير مثبت، ميزات كشف NSFW وتحليل الفيديو غير متاحة.") -# ---------- Configuration ---------- -app = Flask(__name__) -app.secret_key = secrets.token_hex(32) -app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=30) -app.config['JSON_AS_ASCII'] = False +try: + from Crypto.Cipher import AES + from Crypto.Util.Padding import pad, unpad + HAS_CRYPTO = True +except ImportError: + HAS_CRYPTO = False + logger.warning("PyCryptodome غير مثبت، تشفير الفيديوهات لن يعمل.") -# Folders -AVATAR_FOLDER = 'avatars' -os.makedirs(AVATAR_FOLDER, exist_ok=True) +from flask import ( + Flask, request, session, g, jsonify, render_template_string, + redirect, url_for, abort, send_from_directory, Response +) +from werkzeug.utils import secure_filename +from werkzeug.security import generate_password_hash, check_password_hash -# Cloudinary +# ---------- تكوين Cloudinary ---------- cloudinary.config( cloud_name="dpylnwrw0", api_key="631276857136451", api_secret="xpehguQcV_7nj0iBXsMNM5PssHE" ) -# Database -DATABASE = 'arc_video.db' -VECTOR_DIM = 10 +# ---------- التهيئة والإعدادات ---------- +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 -# Allowed extensions +# إعدادات رفع الملفات +UPLOAD_FOLDER = os.path.join(os.getcwd(), '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'} +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 = {} -# ---------- Database helpers ---------- +# ---------- دوال قاعدة البيانات ---------- def get_db(): db = getattr(g, '_database', None) if db is None: @@ -78,11 +127,12 @@ def close_connection(exception): db.close() def init_db(): + """إنشاء قاعدة البيانات وجميع الجداول""" with app.app_context(): db = get_db() cursor = db.cursor() - - # Users + + # جدول المستخدمين cursor.execute(''' CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -92,39 +142,75 @@ def init_db(): avatar TEXT DEFAULT 'default.jpg', cover_image TEXT DEFAULT 'default_cover.jpg', bio TEXT, + phone TEXT, + gender TEXT, + country TEXT, + 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, + referred_by INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_active TIMESTAMP DEFAULT CURRENT_TIMESTAMP, xp INTEGER DEFAULT 0, - level INTEGER DEFAULT 1 + level INTEGER DEFAULT 1, + affiliate_balance INTEGER DEFAULT 0, + affiliate_clicks INTEGER DEFAULT 0, + affiliate_conversions INTEGER DEFAULT 0, + is_developer BOOLEAN DEFAULT 0, + developer_api_key TEXT, + FOREIGN KEY(referred_by) REFERENCES users(id) ) ''') - - # Follows + + # جدول إعدادات المستخدم + cursor.execute(''' + CREATE TABLE IF NOT EXISTS user_settings ( + user_id INTEGER PRIMARY KEY, + privacy_profile TEXT DEFAULT 'public', + privacy_videos TEXT DEFAULT 'public', + notifications_likes BOOLEAN DEFAULT 1, + notifications_comments BOOLEAN DEFAULT 1, + notifications_follows BOOLEAN DEFAULT 1, + notifications_messages BOOLEAN DEFAULT 1, + dark_mode BOOLEAN DEFAULT 1, + language TEXT DEFAULT 'ar', + FOREIGN KEY(user_id) REFERENCES users(id) + ) + ''') + + # جدول المتابعات cursor.execute(''' CREATE TABLE IF NOT EXISTS follows ( user_id INTEGER, follower_id INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (user_id, follower_id), FOREIGN KEY(user_id) REFERENCES users(id), - FOREIGN KEY(follower_id) REFERENCES users(id) + FOREIGN KEY(follower_id) REFERENCES users(id), + PRIMARY KEY (user_id, follower_id) ) ''') - - # Videos (Cloudinary) + + # جدول الفيديوهات (مع إضافة حقول Cloudinary) cursor.execute(''' CREATE TABLE IF NOT EXISTS videos ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, - cloudinary_url TEXT NOT NULL, + filename TEXT NOT NULL, + filepath TEXT NOT NULL, + cloudinary_url TEXT, cloudinary_public_id TEXT, - thumbnail_url TEXT, + thumbnail TEXT, title TEXT, description TEXT, + duration INTEGER DEFAULT 0, + music TEXT, allow_comments BOOLEAN DEFAULT 1, + allow_duet BOOLEAN DEFAULT 1, + allow_stitch BOOLEAN DEFAULT 1, visibility TEXT DEFAULT 'public', upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, views INTEGER DEFAULT 0, @@ -133,30 +219,41 @@ def init_db(): saves_count INTEGER DEFAULT 0, comments_count INTEGER DEFAULT 0, vector BLOB, + 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, - FOREIGN KEY(user_id) REFERENCES users(id) + processed_for_ai BOOLEAN DEFAULT 0, + FOREIGN KEY(user_id) REFERENCES users(id), + FOREIGN KEY(challenge_id) REFERENCES challenges(id) ) ''') - - # Interactions + + # جدول التفاعلات cursor.execute(''' CREATE TABLE IF NOT EXISTS interactions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, video_id INTEGER NOT NULL, liked BOOLEAN DEFAULT 0, watched BOOLEAN DEFAULT 0, - saved BOOLEAN DEFAULT 0, shared BOOLEAN DEFAULT 0, + saved BOOLEAN DEFAULT 0, + reported BOOLEAN DEFAULT 0, watch_time INTEGER DEFAULT 0, + voted_challenge BOOLEAN DEFAULT 0, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (user_id, video_id), FOREIGN KEY(user_id) REFERENCES users(id), - FOREIGN KEY(video_id) REFERENCES videos(id) + FOREIGN KEY(video_id) REFERENCES videos(id), + UNIQUE(user_id, video_id) ) ''') - - # Comments + + # جدول التعليقات cursor.execute(''' CREATE TABLE IF NOT EXISTS comments ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -165,13 +262,17 @@ def init_db(): parent_id INTEGER, comment_text TEXT NOT NULL, likes_count INTEGER DEFAULT 0, + is_pinned BOOLEAN DEFAULT 0, + is_reported BOOLEAN DEFAULT 0, + report_count INTEGER DEFAULT 0, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(user_id) REFERENCES users(id), FOREIGN KEY(video_id) REFERENCES videos(id), FOREIGN KEY(parent_id) REFERENCES comments(id) ) ''') - + + # جدول إعجابات التعليقات cursor.execute(''' CREATE TABLE IF NOT EXISTS comment_likes ( user_id INTEGER NOT NULL, @@ -181,815 +282,3312 @@ def init_db(): FOREIGN KEY(comment_id) REFERENCES comments(id) ) ''') - - # Hashtags + + # جدول الهاشتاغات cursor.execute(''' CREATE TABLE IF NOT EXISTS hashtags ( id INTEGER PRIMARY KEY AUTOINCREMENT, tag TEXT UNIQUE NOT NULL, - usage_count INTEGER DEFAULT 1 + usage_count INTEGER DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') - + + # جدول ربط الفيديوهات بالهاشتاغات cursor.execute(''' CREATE TABLE IF NOT EXISTS video_hashtags ( video_id INTEGER NOT NULL, hashtag_id INTEGER NOT NULL, - PRIMARY KEY (video_id, hashtag_id), FOREIGN KEY(video_id) REFERENCES videos(id), - FOREIGN KEY(hashtag_id) REFERENCES hashtags(id) + FOREIGN KEY(hashtag_id) REFERENCES hashtags(id), + PRIMARY KEY (video_id, hashtag_id) ) ''') - - # Notifications (simplified) + + # جدول الإشعارات cursor.execute(''' CREATE TABLE IF NOT EXISTS notifications ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, from_user_id INTEGER, - type TEXT, + type TEXT NOT NULL, content TEXT, video_id INTEGER, + comment_id INTEGER, + is_read BOOLEAN DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(user_id) REFERENCES users(id), + FOREIGN KEY(from_user_id) REFERENCES users(id), + FOREIGN KEY(video_id) REFERENCES videos(id), + FOREIGN KEY(comment_id) REFERENCES comments(id) + ) + ''') + + # جدول الرسائل الخاصة + cursor.execute(''' + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + sender_id INTEGER NOT NULL, + receiver_id INTEGER NOT NULL, + message TEXT NOT NULL, is_read BOOLEAN DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(sender_id) REFERENCES users(id), + FOREIGN KEY(receiver_id) REFERENCES users(id) + ) + ''') + + # جدول التحديات + cursor.execute(''' + CREATE TABLE IF NOT EXISTS challenges ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + description TEXT, + prize_coins INTEGER DEFAULT 50, + start_date DATE, + end_date DATE, + is_active BOOLEAN DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') - - # XP + + # جدول مشاركات التحديات + cursor.execute(''' + CREATE TABLE IF NOT EXISTS challenge_participants ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + challenge_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + video_id INTEGER NOT NULL, + votes INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(challenge_id) REFERENCES challenges(id), + FOREIGN KEY(user_id) REFERENCES users(id), + FOREIGN KEY(video_id) REFERENCES videos(id) + ) + ''') + + # جدول الإحالات + cursor.execute(''' + CREATE TABLE IF NOT EXISTS referrals ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + referrer_id INTEGER NOT NULL, + referred_id INTEGER NOT NULL, + reward_coins INTEGER DEFAULT 50, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(referrer_id) REFERENCES users(id), + FOREIGN KEY(referred_id) REFERENCES users(id), + UNIQUE(referred_id) + ) + ''') + + # جدول نقرات الإحالة + cursor.execute(''' + CREATE TABLE IF NOT EXISTS affiliate_clicks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + referrer_id INTEGER NOT NULL, + ip TEXT, + user_agent TEXT, + clicked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + converted BOOLEAN DEFAULT 0, + FOREIGN KEY(referrer_id) REFERENCES users(id) + ) + ''') + + # جدول المعاملات + cursor.execute(''' + CREATE TABLE IF NOT EXISTS transactions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + type TEXT NOT NULL, + amount INTEGER NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(user_id) REFERENCES users(id) + ) + ''') + + # جدول البلاغات + cursor.execute(''' + CREATE TABLE IF NOT EXISTS reports ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + reporter_id INTEGER NOT NULL, + reported_user_id INTEGER, + reported_video_id INTEGER, + reported_comment_id INTEGER, + reason TEXT NOT NULL, + status TEXT DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(reporter_id) REFERENCES users(id), + FOREIGN KEY(reported_user_id) REFERENCES users(id), + FOREIGN KEY(reported_video_id) REFERENCES videos(id), + FOREIGN KEY(reported_comment_id) REFERENCES comments(id) + ) + ''') + + # جدول البث المباشر + 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 ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + video_id INTEGER NOT NULL, + watch_time INTEGER DEFAULT 0, + completed BOOLEAN DEFAULT 0, + viewed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(user_id) REFERENCES users(id), + FOREIGN KEY(video_id) REFERENCES videos(id) + ) + ''') + + # جدول نقاط الخبرة cursor.execute(''' CREATE TABLE IF NOT EXISTS user_xp ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, action TEXT NOT NULL, xp_gained INTEGER NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(user_id) REFERENCES users(id) + ) + ''') + + # جدول المكافآت اليومية + cursor.execute(''' + CREATE TABLE IF NOT EXISTS daily_rewards ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + reward_date DATE NOT NULL, + reward_coins INTEGER NOT NULL, + streak INTEGER DEFAULT 1, + claimed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id, reward_date) + ) + ''') + + # جدول إضافات المطورين + cursor.execute(''' + CREATE TABLE IF NOT EXISTS developer_plugins ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + name TEXT NOT NULL, + filename TEXT NOT NULL, + code TEXT NOT NULL, + is_active BOOLEAN DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id, name) + ) + ''') + + # جدول جلسات المطورين + cursor.execute(''' + CREATE TABLE IF NOT EXISTS developer_sessions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + session_token TEXT UNIQUE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + expires_at TIMESTAMP NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) + ) + ''') + + # جدول أحداث التحليلات + cursor.execute(''' + CREATE TABLE IF NOT EXISTS analytics_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + event_name TEXT NOT NULL, + event_data TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') - + db.commit() - - # Create default users + + # إنشاء المستخدمين الافتراضيين create_default_users() create_default_avatars() + create_default_challenges() + +# ---------- دوال مساعدة ---------- +def generate_referral_code(): + return secrets.token_hex(4).upper() + +def random_vector(dim=VECTOR_DIM): + vec = np.random.randn(dim).astype(np.float32) + return vec.tobytes() + +def bytes_to_vector(b): + if b is None: + return np.zeros(VECTOR_DIM, dtype=np.float32) + return np.frombuffer(b, dtype=np.float32) + +def allowed_video(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_VIDEO_EXTENSIONS + +def allowed_image(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_IMAGE_EXTENSIONS + +def allowed_plugin(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_PLUGIN_EXTENSIONS def create_default_users(): db = get_db() - if not db.execute("SELECT * FROM users WHERE username='admin'").fetchone(): + # مستخدم admin + admin = db.execute("SELECT * FROM users WHERE username = 'admin'").fetchone() + if not admin: hashed = generate_password_hash('admin123') - code = secrets.token_hex(4).upper() - db.execute("INSERT INTO users (username, email, password_hash, is_admin, referral_code) VALUES (?,?,?,?,?)", - ('admin', 'admin@arc.com', hashed, 1, code)) - uid = db.execute("SELECT last_insert_rowid()").fetchone()[0] + referral = generate_referral_code() + db.execute(''' + INSERT INTO users (username, email, password_hash, is_admin, referral_code, is_developer) + VALUES (?, ?, ?, ?, ?, ?) + ''', ('admin', 'admin@arc.com', hashed, 1, referral, 1)) + admin_id = db.execute("SELECT last_insert_rowid()").fetchone()[0] + db.execute('INSERT INTO user_settings (user_id) VALUES (?)', (admin_id,)) db.commit() - - if not db.execute("SELECT * FROM users WHERE username='test'").fetchone(): + + # مستخدم test + test = db.execute("SELECT * FROM users WHERE username = 'test'").fetchone() + if not test: hashed = generate_password_hash('test123') - code = secrets.token_hex(4).upper() - db.execute("INSERT INTO users (username, email, password_hash, referral_code) VALUES (?,?,?,?)", - ('test', 'test@arc.com', hashed, code)) + referral = generate_referral_code() + db.execute(''' + INSERT INTO users (username, email, password_hash, referral_code) + VALUES (?, ?, ?, ?) + ''', ('test', 'test@arc.com', hashed, referral)) + test_id = db.execute("SELECT last_insert_rowid()").fetchone()[0] + db.execute('INSERT INTO user_settings (user_id) VALUES (?)', (test_id,)) db.commit() def create_default_avatars(): - default_avatar = os.path.join(AVATAR_FOLDER, 'default.jpg') - default_cover = os.path.join(AVATAR_FOLDER, 'default_cover.jpg') - if not os.path.exists(default_avatar): - if HAS_PIL: - img = Image.new('RGB', (200,200), color=(255,45,85)) - d = ImageDraw.Draw(img) - d.ellipse((20,20,180,180), fill=(255,255,255)) - d.text((70,90), "ARC", fill=(255,45,85)) - img.save(default_avatar) - else: - with open(default_avatar, 'w') as f: f.write('avatar') - if not os.path.exists(default_cover): - if HAS_PIL: - img = Image.new('RGB', (800,200), color=(30,30,30)) + default_avatar_path = os.path.join(AVATAR_FOLDER, 'default.jpg') + default_cover_path = os.path.join(AVATAR_FOLDER, 'default_cover.jpg') + + if os.path.exists(default_avatar_path) and os.path.exists(default_cover_path): + return + + if HAS_PIL: + try: + # صورة افتراضية للبروفايل + img = Image.new('RGB', (200, 200), color=(255, 45, 85)) d = ImageDraw.Draw(img) - d.text((350,80), "ARC VIDEO", fill=(255,45,85)) - img.save(default_cover) - else: - with open(default_cover, 'w') as f: f.write('cover') - -# ---------- Utility functions ---------- -def allowed_video(filename): - return '.' in filename and filename.rsplit('.',1)[1].lower() in ALLOWED_VIDEO_EXTENSIONS + d.ellipse((20, 20, 180, 180), fill=(255, 255, 255)) + d.text((70, 90), "ARC", fill=(255, 45, 85)) + img.save(default_avatar_path) + + # صورة غلاف افتراضية + cover = Image.new('RGB', (800, 200), color=(30, 30, 30)) + d = ImageDraw.Draw(cover) + 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: + f.write('default cover placeholder') + else: + with open(default_avatar_path, 'w') as f: + f.write('default avatar placeholder') + with open(default_cover_path, 'w') as f: + f.write('default cover placeholder') -def allowed_image(filename): - return '.' in filename and filename.rsplit('.',1)[1].lower() in ALLOWED_IMAGE_EXTENSIONS +def create_default_challenges(): + db = get_db() + challenges = db.execute("SELECT * FROM challenges").fetchall() + if not challenges: + today = datetime.now().strftime('%Y-%m-%d') + next_week = (datetime.now() + timedelta(days=7)).strftime('%Y-%m-%d') + challenges_data = [ + ('تحدي الرقص', 'ارقص أفضل رقصة وإربح 100 عملة', 100, today, next_week), + ('تحدي الطبخ', 'اطبخ ألذ طبق', 150, today, next_week), + ('تحدي الضحك', 'أضحكنا من قلبك', 50, today, next_week), + ('تحدي التقليد', 'قلد أي شخصية مشهورة', 200, today, next_week), + ('تحدي المواهب', 'أظهر موهبتك الفريدة', 300, today, next_week) + ] + for c in challenges_data: + db.execute(''' + INSERT INTO challenges (title, description, prize_coins, start_date, end_date) + VALUES (?, ?, ?, ?, ?) + ''', c) + db.commit() -def random_vector(dim=VECTOR_DIM): - return np.random.randn(dim).astype(np.float32).tobytes() +# ---------- دوال الذكاء الاصطناعي الفعلية ---------- +def analyze_video_nsfw(video_path): + """تحليل فيديو للكشف عن محتوى غير مناسب باستخدام OpenCV""" + if not HAS_CV2: + return 0.0 + + try: + cap = cv2.VideoCapture(video_path) + if not cap.isOpened(): + return 0.0 + + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + if total_frames == 0: + 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)] + + skin_tone_count = 0 + for idx in frame_indices: + 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: # إذا كانت نسبة البشرة عالية + skin_tone_count += 1 + + cap.release() + + # حساب درجة NSFW بناءً على نسبة الإطارات التي تحتوي بشرة كثيرة + nsfw_score = skin_tone_count / sample_frames + return min(nsfw_score, 1.0) + + except Exception as e: + logger.error(f"Error in analyze_video_nsfw: {e}") + return 0.0 -def extract_video_features(path): +def extract_video_features(video_path): + """استخراج خصائص من الفيديو لتوليد متجه""" if not HAS_CV2: return random_vector() + try: - cap = cv2.VideoCapture(path) + cap = cv2.VideoCapture(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) / 255.0 + 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 + step = len(features) // VECTOR_DIM features = features[::step][:VECTOR_DIM] - else: + elif len(features) < VECTOR_DIM: features = np.pad(features, (0, VECTOR_DIM - len(features))) + return features.tobytes() - except: + + except Exception as e: + logger.error(f"Error in extract_video_features: {e}") return random_vector() -def analyze_nsfw(path): - if not HAS_CV2: - return 0.0 - try: - cap = cv2.VideoCapture(path) - total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) - if total == 0: - return 0.0 - samples = min(10, total) - indices = [int(i*total/samples) for i in range(samples)] - skin = 0 - for idx in indices: - cap.set(cv2.CAP_PROP_POS_FRAMES, idx) - ret, frame = cap.read() - if ret: - hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) - lower = np.array([0,20,70]) - upper = np.array([20,255,255]) - mask = cv2.inRange(hsv, lower, upper) - perc = cv2.countNonZero(mask) / (frame.shape[0]*frame.shape[1]) * 100 - if perc > 30: - skin += 1 - cap.release() - return skin / samples - except: - return 0.0 - -def generate_auto_tags(path, title, desc): +def generate_auto_tags(video_path, title, description): + """توليد وسوم تلقائية من النص وتحليل الفيديو""" tags = set() - text = f"{title} {desc}" - for word in re.findall(r'#(\w+)', text): - tags.add(word) - for word in re.findall(r'[\w]+', text): - if len(word) > 2 and word.isalpha(): - tags.add(word.lower()) + + # استخراج الكلمات من العنوان والوصف + 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'] + + for word in all_words: + word_lower = word.lower() + if len(word_lower) > 2 and word_lower not in tags: + 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) + + if brightness > 200: + tags.add('مشرق') + tags.add('bright') + elif brightness < 50: + tags.add('داكن') + tags.add('dark') + + # تحليل الحواف + edges = cv2.Canny(gray, 50, 150) + edge_density = np.count_nonzero(edges) / edges.size + + if edge_density > 0.3: + tags.add('تفاصيل') + tags.add('details') + else: + tags.add('بسيط') + tags.add('simple') + + cap.release() + except: + pass + + # تحويل إلى JSON return json.dumps(list(tags)[:15]) -def add_xp(user_id, action, amount): - db = get_db() - db.execute("INSERT INTO user_xp (user_id, action, xp_gained) VALUES (?,?,?)", - (user_id, action, amount)) - db.execute("UPDATE users SET xp = xp + ? WHERE id = ?", (amount, user_id)) - user = db.execute("SELECT xp FROM users WHERE id=?", (user_id,)).fetchone() - if user: - new_level = int((user['xp'] // 100) ** 0.5) + 1 - db.execute("UPDATE users SET level = ? WHERE id=?", (new_level, user_id)) - db.commit() +# ---------- دوال التشفير الفعلية ---------- +def encrypt_video_file(input_path, output_path=None, key=None): + """تشفير ملف فيديو باستخدام AES-256""" + if not HAS_CRYPTO: + 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 + + except Exception as e: + logger.error(f"Encryption error: {e}") + return None, None -def add_notification(user_id, from_id, ntype, content, video_id=None): - if user_id == from_id: - return - db = get_db() - db.execute("INSERT INTO notifications (user_id, from_user_id, type, content, video_id) VALUES (?,?,?,?,?)", - (user_id, from_id, ntype, content, video_id)) - db.commit() +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_coins(user_id, amount, desc): - db = get_db() - db.execute("UPDATE users SET coins = coins + ? WHERE id=?", (amount, user_id)) - db.commit() +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 -# ---------- Cloudinary functions ---------- -def upload_video_to_cloudinary(file_path): +# ---------- دوال Cloudinary ---------- +def upload_video_to_cloudinary(file_path, public_id=None): + """رفع فيديو إلى Cloudinary وإرجاع الرابط و public_id""" try: - resp = cloudinary.uploader.upload(file_path, resource_type="video", folder="arc_videos") - return resp.get('secure_url'), resp.get('public_id') + response = cloudinary.uploader.upload( + file_path, + resource_type="video", + public_id=public_id, + folder="arc_videos", + eager=[ + {"width": 300, "height": 300, "crop": "pad", "audio_codec": "none"}, + {"width": 160, "height": 100, "crop": "crop", "gravity": "south", "audio_codec": "none"} + ], + eager_async=True + ) + return response.get('secure_url'), response.get('public_id') except Exception as e: - print("Cloudinary error:", e) + logger.error(f"Cloudinary upload error: {e}") return None, None -def video_thumbnail(public_id): - return cloudinary.CloudinaryVideo(public_id).video_thumbnail_url(width=300, height=300) +def generate_video_thumbnail(video_public_id): + """توليد رابط الصورة المصغرة من Cloudinary""" + return cloudinary.CloudinaryVideo(video_public_id).video_thumbnail_url(width=300, height=300) -def watermark_url(public_id, text="ARC"): - return cloudinary.CloudinaryVideo(public_id).video_url( +def add_watermark_to_cloudinary_video(public_id, text="ARC"): + """إنشاء رابط فيديو مع علامة مائية نصية باستخدام التحويلات السحابية""" + transformed_url = cloudinary.CloudinaryVideo(public_id).video_url( transformation=[ {"width": 500, "crop": "scale"}, - {"overlay": {"font_family": "Arial", "font_size": 40, "text": text}, - "gravity": "south_east", "opacity": 60} + {"overlay": {"font_family": "Arial", "font_size": 40, "text": text}, "gravity": "south_east", "opacity": 60} ] ) + return transformed_url -# ---------- Decorators ---------- -def login_required(f): - @wraps(f) - def decorated(*args, **kwargs): - if 'user_id' not in session: - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({'error': 'unauthorized', 'redirect': '/login'}), 401 - return redirect(url_for('login')) - return f(*args, **kwargs) - return decorated - -# ---------- Routes: Auth ---------- -@app.route('/') -def index(): - if 'user_id' not in session: - return redirect(url_for('login')) - return render_template_string(MAIN_TEMPLATE, session=session) +# ---------- دوال نقاط الخبرة والمكافآت ---------- +def calculate_level_from_xp(xp): + """حساب المستوى بناءً على XP""" + if xp < 100: + return 1 + return int((xp // 100) ** 0.5) + 1 -@app.route('/login', methods=['GET','POST']) -def login(): - if request.method == 'POST': - username = request.form.get('username','') - password = request.form.get('password','') - db = get_db() - user = db.execute("SELECT * FROM users WHERE username=? OR email=?", (username,username)).fetchone() - if user and check_password_hash(user['password_hash'], password): - session['user_id'] = user['id'] - session['username'] = user['username'] - session['is_admin'] = user['is_admin'] - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({'success': True, 'redirect':'/'}) - return redirect(url_for('index')) - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({'success': False, 'error':'بيانات غير صحيحة'}), 401 - return render_template_string(LOGIN_PAGE, error='بيانات غير صحيحة') - return render_template_string(LOGIN_PAGE) +def add_xp(user_id, action, xp_amount): + """إضافة نقاط خبرة للمستخدم""" + db = get_db() + try: + db.execute(''' + INSERT INTO user_xp (user_id, action, xp_gained) + VALUES (?, ?, ?) + ''', (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']) + db.execute('UPDATE users SET level = ? WHERE id = ?', (new_level, user_id)) + + db.commit() + return True + except Exception as e: + logger.error(f"Error adding XP: {e}") + return False -@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','') - if not username or not email or not password: - return render_template_string(REGISTER_PAGE, error='جميع الحقول مطلوبة') - if password != confirm: - return render_template_string(REGISTER_PAGE, error='كلمات المرور غير متطابقة') - if len(password) < 6: - return render_template_string(REGISTER_PAGE, error='كلمة المرور قصيرة') - db = get_db() - if db.execute("SELECT id FROM users WHERE username=? OR email=?", (username,email)).fetchone(): - return render_template_string(REGISTER_PAGE, error='المستخدم موجود بالفعل') - hashed = generate_password_hash(password) - code = secrets.token_hex(4).upper() - db.execute("INSERT INTO users (username, email, password_hash, referral_code) VALUES (?,?,?,?)", - (username, email, hashed, code)) - uid = db.execute("SELECT last_insert_rowid()").fetchone()[0] +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 = ? + ''', (user_id, today.isoformat())).fetchone() + + if existing: + return False, "تم استلام المكافأة اليوم مسبقاً" + + # حساب streak + yesterday = (today - timedelta(days=1)).isoformat() + last = db.execute(''' + SELECT streak FROM daily_rewards + WHERE user_id = ? AND reward_date = ? + ''', (user_id, yesterday)).fetchone() + + streak = (last['streak'] + 1) if last else 1 + + # عملات عشوائية + coins = random.randint(50, 100) * streak + + db.execute(''' + INSERT INTO daily_rewards (user_id, reward_date, reward_coins, streak) + VALUES (?, ?, ?, ?) + ''', (user_id, today.isoformat(), coins, streak)) + + db.execute('UPDATE users SET coins = coins + ? WHERE id = ?', (coins, user_id)) + + db.execute(''' + INSERT INTO transactions (user_id, type, amount, description) + VALUES (?, 'earn', ?, 'مكافأة يومية') + ''', (user_id, coins)) + db.commit() - add_xp(uid, 'register', 50) - return redirect(url_for('login')) - return render_template_string(REGISTER_PAGE) + return True, coins, streak + + except Exception as e: + logger.error(f"Error claiming daily reward: {e}") + return False, str(e) -@app.route('/logout') +# ---------- دوال التسويق بالعمولة ---------- +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)) + db.commit() + 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() + if not referrer: + return False + + db.execute(''' + INSERT INTO affiliate_clicks (referrer_id, ip, user_agent) + VALUES (?, ?, ?) + ''', (referrer['id'], 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") + + return True + + except Exception as e: + logger.error(f"Error tracking affiliate click: {e}") + 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 + ORDER BY clicked_at DESC LIMIT 1 + ''', (referrer['id'],)) + + db.execute('UPDATE users SET affiliate_conversions = affiliate_conversions + 1 WHERE id = ?', (referrer['id'],)) + + # مكافآت + coins_referrer = 100 + coins_referred = 50 + + db.execute('UPDATE users SET coins = coins + ?, affiliate_balance = affiliate_balance + ? WHERE id = ?', + (coins_referrer, coins_referrer, referrer['id'])) + db.execute('UPDATE users SET coins = coins + ? WHERE id = ?', (coins_referred, new_user_id)) + + db.execute(''' + INSERT INTO transactions (user_id, type, amount, description) + VALUES (?, 'affiliate', ?, 'مكافأة إحالة') + ''', (referrer['id'], coins_referrer)) + + db.execute(''' + INSERT INTO transactions (user_id, type, amount, description) + VALUES (?, 'affiliate', ?, 'مكافأة تسجيل عن طريق رابط') + ''', (new_user_id, coins_referred)) + + db.execute(''' + INSERT INTO referrals (referrer_id, referred_id, reward_coins) + VALUES (?, ?, ?) + ''', (referrer['id'], new_user_id, coins_referrer)) + + 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") + + 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(''' + INSERT INTO analytics_events (user_id, event_name, event_data) + VALUES (?, ?, ?) + ''', (user_id, event_name, json.dumps(event_data) if event_data else None)) + db.commit() + except Exception as e: + 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] + total_views = db.execute('SELECT SUM(views) FROM videos WHERE user_id = ?', (user_id,)).fetchone()[0] or 0 + total_likes = db.execute('SELECT SUM(likes_count) FROM videos WHERE user_id = ?', (user_id,)).fetchone()[0] or 0 + total_comments = db.execute('SELECT SUM(comments_count) FROM videos WHERE user_id = ?', (user_id,)).fetchone()[0] or 0 + followers = db.execute('SELECT COUNT(*) FROM follows WHERE user_id = ?', (user_id,)).fetchone()[0] + following = db.execute('SELECT COUNT(*) FROM follows WHERE follower_id = ?', (user_id,)).fetchone()[0] + + user = db.execute(''' + SELECT xp, level, coins, diamonds, affiliate_balance, affiliate_clicks, affiliate_conversions + FROM users WHERE id = ? + ''', (user_id,)).fetchone() + + return { + 'videos': videos, + 'total_views': total_views, + 'total_likes': total_likes, + 'total_comments': total_comments, + 'followers': followers, + 'following': following, + 'xp': user['xp'], + 'level': user['level'], + 'coins': user['coins'], + 'diamonds': user['diamonds'], + 'affiliate_balance': user['affiliate_balance'], + 'affiliate_clicks': user['affiliate_clicks'], + 'affiliate_conversions': user['affiliate_conversions'] + } + except Exception as e: + 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() + db.execute(''' + INSERT INTO developer_sessions (user_id, session_token, expires_at) + VALUES (?, ?, ?) + ''', (user_id, token, expires.isoformat())) + db.commit() + return token + +def validate_developer_session(token): + """التحقق من صحة جلسة المطور""" + db = get_db() + session = db.execute(''' + SELECT user_id, expires_at FROM developer_sessions + WHERE session_token = ? AND expires_at > ? + ''', (token, datetime.now().isoformat())).fetchone() + + if session: + return session['user_id'] + return None + +def execute_code_in_sandbox(code): + """تنفيذ كود Python في بيئة معزولة""" + local_vars = {} + try: + # إضافة بعض الدوال المساعدة + allowed_builtins = { + 'print': print, + 'len': len, + 'range': range, + 'int': int, + 'str': str, + 'float': float, + 'list': list, + 'dict': dict, + 'set': set, + 'tuple': tuple, + 'bool': bool, + 'enumerate': enumerate, + 'zip': zip, + 'sum': sum, + 'max': max, + 'min': min, + 'abs': abs, + 'round': round, + 'sorted': sorted, + 'reversed': reversed, + 'any': any, + 'all': all, + } + + 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] + + return {"success": True, "result": str(result) if result is not None else None} + + except Exception as e: + 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(''' + INSERT INTO developer_plugins (user_id, name, code, filename) + VALUES (?, ?, ?, ?) + ''', (user_id, name, code, name + '.py')) + + db.commit() + + # تحميل في الذاكرة + load_plugin_into_memory(user_id, name, code) + + return True + + except Exception as e: + logger.error(f"Error installing plugin: {e}") + return False + +def load_plugin_into_memory(user_id, name, code): + """تحميل إضافة في الذاكرة""" + try: + namespace = {} + exec(code, namespace) + developer_plugins[f"{user_id}_{name}"] = namespace + logger.info(f"Plugin {name} loaded for user {user_id}") + return True + except Exception as e: + logger.error(f"Error loading plugin {name}: {e}") + 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() + for p in plugins: + load_plugin_into_memory(p['user_id'], p['name'], p['code']) + logger.info(f"Loaded {len(plugins)} plugins") + except Exception as e: + 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]) + if uid == user_id and hook_name in namespace: + try: + res = namespace[hook_name](*args, **kwargs) + results.append(res) + except Exception as e: + 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 + db = get_db() + db.execute(''' + INSERT INTO notifications (user_id, from_user_id, type, content, video_id, comment_id) + VALUES (?, ?, ?, ?, ?, ?) + ''', (user_id, from_user_id, n_type, content, video_id, comment_id)) + db.commit() + + if user_id in notification_queues: + notification_queues[user_id].put({ + 'type': n_type, + 'content': content, + 'from_user': from_user_id, + 'video_id': video_id, + 'timestamp': datetime.now().isoformat() + }) + +def add_coins(user_id, amount, description): + db = get_db() + db.execute('UPDATE users SET coins = coins + ? WHERE id = ?', (amount, user_id)) + db.execute(''' + INSERT INTO transactions (user_id, type, amount, description) + VALUES (?, 'earn', ?, ?) + ''', (user_id, amount, description)) + db.commit() + +def deduct_coins(user_id, amount, description): + db = get_db() + user = db.execute('SELECT coins FROM users WHERE id = ?', (user_id,)).fetchone() + if user and user['coins'] >= amount: + db.execute('UPDATE users SET coins = coins - ? WHERE id = ?', (amount, user_id)) + db.execute(''' + INSERT INTO transactions (user_id, type, amount, description) + VALUES (?, 'spend', ?, ?) + ''', (user_id, amount, description)) + db.commit() + return True + return False + +# ---------- نظام التوصيات ---------- +def get_user_interest_vector(user_id): + db = get_db() + rows = db.execute(''' + SELECT v.vector FROM videos v + JOIN interactions i ON v.id = i.video_id + WHERE i.user_id = ? AND i.liked = 1 AND v.vector IS NOT NULL + ORDER BY i.timestamp DESC + LIMIT 50 + ''', (user_id,)).fetchall() + + if not rows: + return np.zeros(VECTOR_DIM, dtype=np.float32) + + vectors = [bytes_to_vector(row['vector']) for row in rows if row['vector']] + if not vectors: + return np.zeros(VECTOR_DIM, dtype=np.float32) + + return np.mean(vectors, axis=0) + +def recommend_videos(user_id, 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] + + interest = get_user_interest_vector(user_id) + + placeholders = ','.join(['?'] * len(watched_ids)) + query = f''' + SELECT v.*, u.username, u.avatar, u.is_verified, + (SELECT COUNT(*) FROM interactions WHERE video_id = v.id AND liked = 1) as likes, + (SELECT COUNT(*) FROM comments WHERE video_id = v.id) as comments + FROM videos v + 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] + 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 + + video_scores.append((score, dict(row))) + + video_scores.sort(key=lambda x: x[0], reverse=True) + return [v for _, v in video_scores[:limit]] + + except Exception as e: + logger.error(f"Error in recommendation: {e}") + rows = db.execute(''' + SELECT v.*, u.username, u.avatar, u.is_verified + FROM videos v + JOIN users u ON v.user_id = u.id + WHERE v.visibility = 'public' + ORDER BY v.upload_time DESC + LIMIT ? OFFSET ? + ''', (limit, offset)).fetchall() + return [dict(row) for row in rows] + +def recommend_friends_videos(user_id, limit=10, offset=0): + """توصية فيديوهات الأصدقاء (المتابَعين)""" + db = get_db() + try: + videos = db.execute(''' + SELECT v.*, u.username, u.avatar, u.is_verified + FROM videos v + JOIN users u ON v.user_id = u.id + WHERE v.user_id IN (SELECT user_id FROM follows WHERE follower_id = ?) + AND v.visibility = 'public' + ORDER BY v.upload_time DESC + LIMIT ? OFFSET ? + ''', (user_id, limit, offset)).fetchall() + return [dict(v) for v in videos] + except Exception as e: + logger.error(f"Error in friends videos: {e}") + return [] + +# ---------- ديكوريتورات ---------- +def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({'error': 'unauthorized', 'redirect': '/login'}), 401 + return redirect(url_for('login')) + return f(*args, **kwargs) + return decorated_function + +def admin_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + return redirect(url_for('login')) + db = get_db() + user = db.execute('SELECT is_admin FROM users WHERE id = ?', (session['user_id'],)).fetchone() + if not user or not user['is_admin']: + abort(403) + return f(*args, **kwargs) + return decorated_function + +def developer_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + return redirect(url_for('login')) + db = get_db() + user = db.execute('SELECT is_developer FROM users WHERE id = ?', (session['user_id'],)).fetchone() + if not user or not user['is_developer']: + abort(403) + 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, session=session) + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + username = request.form.get('username', '') + password = request.form.get('password', '') + remember = request.form.get('remember', False) + + db = get_db() + user = db.execute('SELECT * FROM users WHERE username = ? OR email = ?', (username, username)).fetchone() + + if user and check_password_hash(user['password_hash'], password): + session['user_id'] = user['id'] + session['username'] = user['username'] + session['is_admin'] = user['is_admin'] + session['is_developer'] = user['is_developer'] + + if remember: + session.permanent = True + + db.execute('UPDATE users SET last_active = CURRENT_TIMESTAMP WHERE id = ?', (user['id'],)) + db.commit() + + track_event(user['id'], 'login', {}) + + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({'success': True, 'redirect': '/'}) + return redirect(url_for('index')) + + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({'success': False, 'error': 'بيانات الدخول غير صحيحة'}), 401 + return render_template_string(LOGIN_PAGE, error='اسم المستخدم أو كلمة المرور غير صحيحة') + + return render_template_string(LOGIN_PAGE) + +@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 not username or not email or not password: + return render_template_string(REGISTER_PAGE, error='جميع الحقول مطلوبة') + + if password != confirm: + return render_template_string(REGISTER_PAGE, error='كلمات المرور غير متطابقة') + + if len(password) < 6: + return render_template_string(REGISTER_PAGE, error='كلمة المرور يجب أن تكون 6 أحرف على الأقل') + + 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='اسم المستخدم أو البريد الإلكتروني موجود بالفعل') + + hashed = generate_password_hash(password) + referral_code = generate_referral_code() + + db.execute(''' + INSERT INTO users (username, email, password_hash, referral_code) + VALUES (?, ?, ?, ?) + ''', (username, email, hashed, referral_code)) + + 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() + return redirect(url_for('login')) + + return render_template_string(REGISTER_PAGE) + +@app.route('/logout') def logout(): + track_event(session.get('user_id'), 'logout', {}) session.clear() return redirect(url_for('login')) -# ---------- Follow system ---------- +@app.route('/forgot-password', methods=['GET', 'POST']) +def forgot_password(): + if request.method == 'POST': + email = request.form.get('email', '') + return render_template_string(FORGOT_PAGE, message='تم إرسال رابط إعادة التعيين إلى بريدك الإلكتروني') + return render_template_string(FORGOT_PAGE) + +# ---------- ملف المستخدم ---------- +@app.route('/profile/') +@login_required +def profile(user_id): + db = get_db() + user = db.execute(''' + SELECT u.*, s.* + 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) + + videos = db.execute(''' + SELECT id, filename, title, thumbnail, cloudinary_url, 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() + + followers_count = db.execute('SELECT COUNT(*) FROM follows WHERE user_id = ?', (user_id,)).fetchone()[0] + following_count = db.execute('SELECT COUNT(*) FROM follows WHERE follower_id = ?', (user_id,)).fetchone()[0] + + is_following = False + if session['user_id'] != user_id: + is_following = db.execute('SELECT * FROM follows WHERE user_id = ? AND follower_id = ?', + (user_id, session['user_id'])).fetchone() is not None + + total_views = db.execute('SELECT SUM(views) FROM videos WHERE user_id = ?', (user_id,)).fetchone()[0] or 0 + total_likes = db.execute('SELECT SUM(likes_count) FROM videos WHERE user_id = ?', (user_id,)).fetchone()[0] or 0 + + videos_list = [] + for v in videos: + video_dict = dict(v) + video_dict['url'] = v['cloudinary_url'] if v['cloudinary_url'] else 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, + session=session) + +@app.route('/settings', methods=['GET', 'POST']) +@login_required +def settings(): + db = get_db() + + 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'])) + + 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() + + return render_template_string(SETTINGS_TEMPLATE, + user=dict(user), + settings=dict(settings) if settings else {}) + +# ---------- نظام المتابعة ---------- @app.route('/follow/', methods=['POST']) @login_required def follow(user_id): if user_id == session['user_id']: - return jsonify({'error':'لا يمكنك متابعة نفسك'}),400 + return jsonify({'error': 'لا يمكنك متابعة نفسك'}), 400 + db = get_db() - db.execute("INSERT OR IGNORE INTO follows (user_id, follower_id) VALUES (?,?)", (user_id, session['user_id'])) + db.execute('INSERT OR IGNORE INTO follows (user_id, follower_id) VALUES (?, ?)', (user_id, session['user_id'])) db.commit() - count = db.execute("SELECT COUNT(*) FROM follows WHERE user_id=?", (user_id,)).fetchone()[0] + + user = db.execute('SELECT username FROM users WHERE id = ?', (session['user_id'],)).fetchone() + add_notification(user_id, session['user_id'], 'follow', f'بدأ {user["username"]} بمتابعتك') + + count = db.execute('SELECT COUNT(*) FROM follows WHERE user_id = ?', (user_id,)).fetchone()[0] + + # XP للمتابعة add_xp(session['user_id'], 'follow', 5) - return jsonify({'status':'ok', 'followers_count':count}) + track_event(session['user_id'], 'follow', {'followed_user_id': user_id}) + + return jsonify({'status': 'ok', 'followers_count': count}) @app.route('/unfollow/', methods=['POST']) @login_required def unfollow(user_id): db = get_db() - db.execute("DELETE FROM follows WHERE user_id=? AND follower_id=?", (user_id, session['user_id'])) + db.execute('DELETE FROM follows WHERE user_id = ? AND follower_id = ?', (user_id, session['user_id'])) db.commit() - count = db.execute("SELECT COUNT(*) FROM follows WHERE user_id=?", (user_id,)).fetchone()[0] - return jsonify({'status':'ok', 'followers_count':count}) + + count = db.execute('SELECT COUNT(*) FROM follows WHERE user_id = ?', (user_id,)).fetchone()[0] + track_event(session['user_id'], 'unfollow', {'unfollowed_user_id': 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/') +@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) + + return jsonify(result) -# ---------- Video upload ---------- -@app.route('/upload', methods=['GET','POST']) +# ---------- نظام الفيديوهات ---------- +@app.route('/upload', methods=['GET', 'POST']) @login_required def upload_video(): if request.method == 'GET': - return render_template_string(UPLOAD_TEMPLATE) + 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]) + if 'video' not in request.files: - return 'لم يتم اختيار فيديو',400 + return 'لم يتم اختيار فيديو', 400 + file = request.files['video'] if file.filename == '': - return 'لم يتم اختيار فيديو',400 - title = request.form.get('title','') - desc = request.form.get('description','') - music = request.form.get('music','') + return 'لم يتم اختيار فيديو', 400 + + title = request.form.get('title', '') + description = request.form.get('description', '') + music = request.form.get('music', '') + challenge_id = request.form.get('challenge_id') allow_comments = request.form.get('allow_comments') == 'on' - visibility = request.form.get('visibility','public') - if not allowed_video(file.filename): - return 'نوع الملف غير مدعوم',400 - - # Save temp - temp = os.path.join('/tmp', f"{uuid.uuid4().hex}_{secure_filename(file.filename)}") - file.save(temp) - - # Upload to Cloudinary - url, public_id = upload_video_to_cloudinary(temp) - if not url: - os.remove(temp) - return 'فشل الرفع إلى السحابة',500 - - thumb = video_thumbnail(public_id) - vector = extract_video_features(temp) - nsfw = analyze_nsfw(temp) - tags = generate_auto_tags(temp, title, desc) - - db = get_db() - cur = db.execute(''' - INSERT INTO videos (user_id, cloudinary_url, cloudinary_public_id, thumbnail_url, - title, description, allow_comments, visibility, vector, nsfw_score, ai_tags) - VALUES (?,?,?,?,?,?,?,?,?,?,?) - ''', (session['user_id'], url, public_id, thumb, title, desc, allow_comments, visibility, - vector, nsfw, tags)) - vid = cur.lastrowid - - # Hashtags - for tag in re.findall(r'#(\w+)', desc): - tag_lower = tag.lower() - db.execute("INSERT OR IGNORE INTO hashtags (tag) VALUES (?)", (tag_lower,)) - h = db.execute("SELECT id FROM hashtags WHERE tag=?", (tag_lower,)).fetchone() - if h: - db.execute("INSERT OR IGNORE INTO video_hashtags (video_id, hashtag_id) VALUES (?,?)", (vid, h['id'])) - db.execute("UPDATE hashtags SET usage_count = usage_count + 1 WHERE id=?", (h['id'],)) - db.commit() - os.remove(temp) - - add_coins(session['user_id'], 10, 'رفع فيديو') - add_xp(session['user_id'], 'upload', 20) + allow_duet = request.form.get('allow_duet') == 'on' + allow_stitch = request.form.get('allow_stitch') == 'on' + visibility = request.form.get('visibility', 'public') + + 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) + + # رفع إلى Cloudinary + cloudinary_url, cloudinary_public_id = upload_video_to_cloudinary(filepath) + thumbnail_url = generate_video_thumbnail(cloudinary_public_id) if cloudinary_public_id else None + + # استخراج الخصائص + vec_bytes = extract_video_features(filepath) + + # تحليل NSFW + nsfw_score = analyze_video_nsfw(filepath) if HAS_CV2 else 0.0 + + # توليد وسوم تلقائية + ai_tags = generate_auto_tags(filepath, 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 + + # إضافة علامة مائية محلية (اختياري) + watermarked_path = add_watermark_to_video(filepath) if HAS_CV2 and HAS_PIL else None + + db = get_db() + cursor = db.execute(''' + INSERT INTO videos ( + user_id, filename, filepath, cloudinary_url, cloudinary_public_id, thumbnail, 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, cloudinary_url, cloudinary_public_id, thumbnail_url, 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)) + + video_id = cursor.lastrowid + + # معالجة الهاشتاغات + hashtags = re.findall(r'#(\w+)', description) + for tag in hashtags: + tag_lower = tag.lower() + db.execute('INSERT OR IGNORE INTO hashtags (tag) VALUES (?)', (tag_lower,)) + tag_row = db.execute('SELECT id FROM hashtags WHERE tag = ?', (tag_lower,)).fetchone() + if tag_row: + db.execute('INSERT OR IGNORE INTO video_hashtags (video_id, hashtag_id) VALUES (?, ?)', + (video_id, tag_row['id'])) + db.execute('UPDATE hashtags SET usage_count = usage_count + 1 WHERE id = ?', (tag_row['id'],)) + + # معالجة التحدي + if challenge_id and challenge_id.isdigit(): + db.execute(''' + INSERT INTO challenge_participants (challenge_id, user_id, video_id) + VALUES (?, ?, ?) + ''', (int(challenge_id), session['user_id'], video_id)) + + 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 - return redirect(url_for('index')) +@app.route('/video/') +@login_required +def video_page(video_id): + return render_template_string(VIDEO_PAGE_TEMPLATE, video_id=video_id) -# ---------- API: Feeds ---------- @app.route('/api/for-you') @login_required def for_you(): - page = int(request.args.get('page',1)) - limit = 10 - offset = (page-1)*limit - db = get_db() - watched = db.execute("SELECT video_id FROM interactions WHERE user_id=? AND watched=1", (session['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=?", (session['user_id'],)).fetchall() - following_ids = [f['user_id'] for f in following] if following else [-1] - placeholders = ','.join(['?']*len(watched_ids)) - query = f''' - SELECT v.*, u.username, u.avatar - FROM videos v - 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, offset] - rows = db.execute(query, params).fetchall() - videos = [dict(r) for r in rows] - # Prioritize followed users (simple reorder) - videos.sort(key=lambda x: (x['user_id'] in following_ids, x['upload_time']), reverse=True) - result = [] - for v in videos[:limit]: - v['url'] = v['cloudinary_url'] - v['avatar_url'] = f"/avatars/{v['avatar']}" - liked = db.execute("SELECT liked FROM interactions WHERE user_id=? AND video_id=?", (session['user_id'], v['id'])).fetchone() - v['liked_by_user'] = liked['liked'] if liked else False - result.append(v) - next_page = page+1 if len(videos) == limit else None - return jsonify({'videos':result, 'next_page':next_page}) - -@app.route('/api/friends') + 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'] = v['cloudinary_url'] if v['cloudinary_url'] else 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/friends-feed') @login_required def friends_feed(): - 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 - FROM videos v - JOIN users u ON v.user_id = u.id - WHERE v.user_id IN (SELECT user_id FROM follows WHERE follower_id=?) AND v.visibility='public' - ORDER BY v.upload_time DESC - LIMIT ? OFFSET ? - ''', (session['user_id'], limit, offset)).fetchall() - result = [] - for v in videos: - v = dict(v) - v['url'] = v['cloudinary_url'] - v['avatar_url'] = f"/avatars/{v['avatar']}" - liked = db.execute("SELECT liked FROM interactions WHERE user_id=? AND video_id=?", (session['user_id'], v['id'])).fetchone() - v['liked_by_user'] = liked['liked'] if liked else False - result.append(v) - next_page = page+1 if len(result) == limit else None - return jsonify({'videos':result, 'next_page':next_page}) + """فيديو الأصدقاء (المتابَعين)""" + try: + page = int(request.args.get('page', 1)) + limit = 10 + offset = (page - 1) * limit + + videos = recommend_friends_videos(session['user_id'], limit, offset) + + result = [] + for v in videos: + video = dict(v) + video['url'] = v['cloudinary_url'] if v['cloudinary_url'] else f'/videos/{v["filename"]}' + video['avatar_url'] = f'/avatars/{v["avatar"]}' + video.pop('vector', None) + + 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) + + return jsonify({'videos': result, 'next_page': page + 1 if len(result) == limit else None}) + + except Exception as e: + logger.error(f"Error in friends_feed: {e}") + return jsonify({'videos': [], 'next_page': None}), 500 @app.route('/api/trending') @login_required def trending(): - 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 - 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: - v = dict(v) - v['url'] = v['cloudinary_url'] - v['avatar_url'] = f"/avatars/{v['avatar']}" - liked = db.execute("SELECT liked FROM interactions WHERE user_id=? AND video_id=?", (session['user_id'], v['id'])).fetchone() - v['liked_by_user'] = liked['liked'] if liked else False - result.append(v) - next_page = page+1 if len(result) == limit else None - return jsonify({'videos':result, 'next_page':next_page}) + 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'] = v['cloudinary_url'] if v['cloudinary_url'] else 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(): - q = request.args.get('q','').strip() - db = get_db() - if not q: - 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() - else: - 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() - # also search hashtags - tag = db.execute("SELECT id FROM hashtags WHERE tag LIKE ?", ('%'+q+'%',)).fetchone() - if tag: - tag_vids = db.execute(''' + 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'] = v['cloudinary_url'] if v['cloudinary_url'] else 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 - JOIN video_hashtags vh ON v.id = vh.video_id - WHERE vh.hashtag_id = ? AND v.visibility='public' + WHERE v.visibility = 'public' + AND (v.title LIKE ? OR v.description LIKE ?) ORDER BY v.views DESC - LIMIT 20 - ''', (tag['id'],)).fetchall() - videos = list(videos) + list(tag_vids) - seen = set() - unique = [] - for v in videos: - if v['id'] not in seen: - seen.add(v['id']) - unique.append(v) - videos = unique[:30] - result = [] - for v in videos: - v = dict(v) - v['url'] = v['cloudinary_url'] - v['avatar_url'] = f"/avatars/{v['avatar']}" - result.append(v) - return jsonify({'videos':result}) + 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'] = v['cloudinary_url'] if v['cloudinary_url'] else 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}) + + except Exception as e: + logger.error(f"Error in search: {e}") + return jsonify({'videos': []}), 500 -# ---------- API: Interactions ---------- @app.route('/api/like/', methods=['POST']) @login_required -def like(video_id): - user_id = session['user_id'] - db = get_db() - row = db.execute("SELECT liked FROM interactions WHERE user_id=? AND video_id=?", (user_id, video_id)).fetchone() - if row and row['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) - ON CONFLICT(user_id, video_id) DO UPDATE SET liked=1 - ''', (user_id, video_id)) - db.execute("UPDATE videos SET likes_count = likes_count + 1 WHERE id=?", (video_id,)) - liked = True - # Notification - video = db.execute("SELECT user_id FROM videos WHERE id=?", (video_id,)).fetchone() - if video and video['user_id'] != user_id: - username = db.execute("SELECT username FROM users WHERE id=?", (user_id,)).fetchone()['username'] - add_notification(video['user_id'], user_id, 'like', f'أعجب {username} بفيديو لك', video_id) - db.commit() - likes = db.execute("SELECT likes_count FROM videos WHERE id=?", (video_id,)).fetchone()['likes_count'] - if liked: - add_xp(user_id, 'like', 2) - return jsonify({'status':'ok', 'liked':liked, 'likes_count':likes}) +def like_video(video_id): + try: + user_id = session['user_id'] + db = get_db() + + interaction = db.execute('SELECT liked FROM interactions WHERE user_id = ? AND 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) + ON CONFLICT(user_id, video_id) DO UPDATE SET liked = 1 + ''', (user_id, video_id)) + db.execute('UPDATE videos SET likes_count = likes_count + 1 WHERE id = ?', (video_id,)) + liked = True + + 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, 'like', f'أعجب {user["username"]} بفيديو لك', video_id) + + db.commit() + + likes_count = db.execute('SELECT likes_count FROM videos WHERE id = ?', (video_id,)).fetchone()['likes_count'] + + if liked: + add_xp(user_id, 'like', 2) + + track_event(user_id, 'like', {'video_id': video_id, 'liked': liked}) + + return jsonify({'status': 'ok', 'liked': liked, 'likes_count': likes_count}) + + except Exception as e: + logger.error(f"Error in like_video: {e}") + return jsonify({'error': str(e)}), 500 @app.route('/api/unlike/', methods=['POST']) @login_required -def unlike(video_id): - # Same as like with opposite effect, but we can just call like and it toggles. - # However, to avoid duplication, we'll implement a toggle in like. So we can keep this for completeness. - return like(video_id) +def unlike_video(video_id): + return like_video(video_id) # نفس الدالة تقوم بالتبديل @app.route('/api/view/', methods=['POST']) @login_required -def view(video_id): - user_id = session['user_id'] - data = request.get_json() or {} - watch_time = data.get('watch_time', 1) - completed = data.get('completed', False) - db = get_db() - row = db.execute("SELECT watched FROM interactions WHERE user_id=? AND video_id=?", (user_id, video_id)).fetchone() - if not row or not row['watched']: +def view_video(video_id): + try: + user_id = session['user_id'] + data = request.get_json() or {} + watch_time = data.get('watch_time', 1) + completed = data.get('completed', False) + + db = get_db() + + existing = db.execute('SELECT watched FROM interactions WHERE user_id = ? AND video_id = ?', + (user_id, video_id)).fetchone() + + if not existing or not existing['watched']: + db.execute(''' + INSERT INTO interactions (user_id, video_id, watched, watch_time) + VALUES (?, ?, 1, ?) + ON CONFLICT(user_id, video_id) DO UPDATE SET + watched = 1, + watch_time = watch_time + ? + ''', (user_id, video_id, watch_time, watch_time)) + db.execute('UPDATE videos SET views = views + 1 WHERE id = ?', (video_id,)) + else: + db.execute(''' + UPDATE interactions SET watch_time = watch_time + ? WHERE user_id = ? AND video_id = ? + ''', (watch_time, user_id, video_id)) + db.execute(''' - INSERT INTO interactions (user_id, video_id, watched, watch_time) - VALUES (?,?,1,?) - ON CONFLICT(user_id, video_id) DO UPDATE SET watched=1, watch_time=watch_time+? - ''', (user_id, video_id, watch_time, watch_time)) - db.execute("UPDATE videos SET views = views + 1 WHERE id=?", (video_id,)) - else: - db.execute("UPDATE interactions SET watch_time = watch_time + ? WHERE user_id=? AND video_id=?", (watch_time, user_id, video_id)) + INSERT INTO video_views (user_id, video_id, watch_time, completed) + VALUES (?, ?, ?, ?) + ''', (user_id, video_id, watch_time, completed)) + + db.commit() + + # XP للمشاهدة (مرة واحدة فقط لكل مشاهدة كاملة) + if completed: + add_xp(user_id, 'complete_view', 3) + + track_event(user_id, 'view_video', {'video_id': video_id, 'watch_time': watch_time, 'completed': completed}) + + return jsonify({'status': 'ok'}) + + except Exception as e: + logger.error(f"Error in view_video: {e}") + return jsonify({'error': str(e)}), 500 + +@app.route('/api/share/', methods=['POST']) +@login_required +def share_video(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)) + + 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'] + + track_event(user_id, 'save', {'video_id': video_id, 'saved': saved_status}) + + return jsonify({'status': 'ok', 'saved': saved_status, 'saves_count': saves}) + + except Exception as e: + 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'] = v['cloudinary_url'] if v['cloudinary_url'] else 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/camera/upload', methods=['POST']) +@login_required +def upload_recorded_video(): + if 'video' not in request.files: + return jsonify({'error': 'لا يوجد فيديو'}), 400 + file = request.files['video'] + if file.filename == '': + return jsonify({'error': 'اسم ملف فارغ'}), 400 + + temp_path = os.path.join('/tmp', f"rec_{uuid.uuid4().hex}.mp4") + file.save(temp_path) + + cloudinary_url, cloudinary_public_id = upload_video_to_cloudinary(temp_path) + thumbnail_url = generate_video_thumbnail(cloudinary_public_id) if cloudinary_public_id else None + + os.remove(temp_path) + + if not cloudinary_url: + return jsonify({'error': 'فشل الرفع'}), 500 + + db = get_db() + unique_name = f"camera_{uuid.uuid4().hex}.mp4" + cursor = db.execute(''' + INSERT INTO videos (user_id, filename, filepath, cloudinary_url, cloudinary_public_id, thumbnail, title, description, visibility) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', (session['user_id'], unique_name, temp_path, cloudinary_url, cloudinary_public_id, thumbnail_url, 'تسجيل كاميرا', '', 'public')) + video_id = cursor.lastrowid db.commit() - if completed: - add_xp(user_id, 'complete_view', 3) - return jsonify({'status':'ok'}) + + add_xp(session['user_id'], 'upload_video', 20) + return jsonify({'status': 'ok', 'video_id': video_id, 'url': cloudinary_url}) + +# ---------- مسار العلامة المائية ---------- +@app.route('/api/watermark/', methods=['POST']) +@login_required +def watermark_video_route(video_id): + try: + db = get_db() + video = db.execute('SELECT cloudinary_public_id FROM videos WHERE id = ?', (video_id,)).fetchone() + if not video or not video['cloudinary_public_id']: + return jsonify({'error': 'فيديو غير موجود أو لا يوجد public_id'}), 404 + + watermarked_url = add_watermark_to_cloudinary_video(video['cloudinary_public_id'], text="ARC") + return jsonify({'watermarked_url': watermarked_url}) + + except Exception as e: + logger.error(f"Watermark error: {e}") + return jsonify({'error': str(e)}), 500 + +# ---------- مسار API للبروفايل (للطبقة الزجاجية) ---------- +@app.route('/api/profile-data/') +@login_required +def profile_data(user_id): + db = get_db() + user = db.execute('SELECT * FROM users WHERE id = ?', (user_id,)).fetchone() + if not user: + return jsonify({'error': 'غير موجود'}), 404 + + videos = db.execute(''' + SELECT id, title, thumbnail, cloudinary_url, views, likes_count, comments_count + FROM videos WHERE user_id = ? AND visibility = 'public' + ORDER BY upload_time DESC LIMIT 30 + ''', (user_id,)).fetchall() + + followers = db.execute('SELECT COUNT(*) FROM follows WHERE user_id = ?', (user_id,)).fetchone()[0] + following = db.execute('SELECT COUNT(*) FROM follows WHERE follower_id = ?', (user_id,)).fetchone()[0] + + is_following = False + if session['user_id'] != user_id: + is_following = db.execute('SELECT * FROM follows WHERE user_id = ? AND follower_id = ?', + (user_id, session['user_id'])).fetchone() is not None + + total_views = db.execute('SELECT SUM(views) FROM videos WHERE user_id = ?', (user_id,)).fetchone()[0] or 0 + + videos_list = [] + for v in videos: + video_dict = dict(v) + video_dict['url'] = v['cloudinary_url'] if v['cloudinary_url'] else f'/videos/{v["filename"]}' + videos_list.append(video_dict) + + return jsonify({ + 'user': dict(user), + 'videos': videos_list, + 'followers_count': followers, + 'following_count': following, + 'is_following': is_following, + 'total_views': total_views + }) + +# ---------- مسارات الطبقة الزجاجية (SPA) ---------- +@app.route('/glass/profile/') +@login_required +def glass_profile(user_id): + return render_template_string(GLASS_PROFILE_TEMPLATE, user_id=user_id, session=session) + +@app.route('/glass/saved') +@login_required +def glass_saved(): + return render_template_string(GLASS_SAVED_TEMPLATE, session=session) + +@app.route('/glass/friends') +@login_required +def glass_friends(): + return render_template_string(GLASS_FRIENDS_TEMPLATE, session=session) + +@app.route('/glass/search') +@login_required +def glass_search(): + return render_template_string(GLASS_SEARCH_TEMPLATE, session=session) + +# ---------- نظام التعليقات ---------- +@app.route('/api/comments/') +@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) + + 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 + reply['avatar_url'] = f'/avatars/{r["avatar"]}' + c['replies'].append(reply) + + 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) + + 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: + 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}) + + 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): + try: + user_id = session['user_id'] + db = get_db() + + liked = db.execute('SELECT * FROM comment_likes WHERE user_id = ? AND comment_id = ?', + (user_id, comment_id)).fetchone() + + if liked: + db.execute('DELETE FROM comment_likes WHERE user_id = ? AND comment_id = ?', (user_id, comment_id)) + db.execute('UPDATE comments SET likes_count = likes_count - 1 WHERE id = ?', (comment_id,)) + liked_status = False + else: + db.execute('INSERT INTO comment_likes (user_id, comment_id) VALUES (?, ?)', (user_id, comment_id)) + db.execute('UPDATE comments SET likes_count = likes_count + 1 WHERE id = ?', (comment_id,)) + liked_status = True + + comment = db.execute('SELECT user_id FROM comments WHERE id = ?', (comment_id,)).fetchone() + if comment and comment['user_id'] != user_id: + user = db.execute('SELECT username FROM users WHERE id = ?', (user_id,)).fetchone() + if user: + add_notification(comment['user_id'], user_id, 'like_comment', f'أعجب {user["username"]} بتعليقك') + + db.commit() + + likes_count = db.execute('SELECT likes_count FROM comments WHERE id = ?', (comment_id,)).fetchone()['likes_count'] + + track_event(user_id, 'like_comment', {'comment_id': comment_id, 'liked': liked_status}) + + return jsonify({'status': 'ok', 'liked': liked_status, 'likes_count': likes_count}) + + except Exception as e: + logger.error(f"Error in like_comment: {e}") + return jsonify({'error': str(e)}), 500 + +@app.route('/api/comment/pin/', methods=['POST']) +@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}) + + return jsonify({'status': 'ok'}) + + except Exception as e: + logger.error(f"Error in pin_comment: {e}") + return jsonify({'error': str(e)}), 500 + +@app.route('/api/comment/delete/', methods=['POST']) +@login_required +def delete_comment(comment_id): + 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 + + 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.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'] = v['cloudinary_url'] if v['cloudinary_url'] else 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"AI recommend error: {e}") + return jsonify({'error': str(e)}), 500 + +@app.route('/api/ai/analyze/', methods=['POST']) +@login_required +def ai_analyze_video(video_id): + """تحليل فيديو""" + 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}) + + return jsonify({'nsfw_score': nsfw_score, 'ai_tags': json.loads(ai_tags)}) + + except Exception as e: + logger.error(f"AI analyze error: {e}") + return jsonify({'error': str(e)}), 500 + +@app.route('/api/ai/generate_tags', methods=['POST']) +@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}) + + except Exception as e: + logger.error(f"AI generate tags error: {e}") + return jsonify({'error': str(e)}), 500 + +# ---------- مسارات التشفير ---------- +@app.route('/api/security/encrypt/', methods=['POST']) +@login_required +def encrypt_video_route(video_id): + """تشفير فيديو""" + 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}) + + except Exception as e: + logger.error(f"Encrypt error: {e}") + return jsonify({'error': str(e)}), 500 + +@app.route('/api/security/watermark/local/', methods=['POST']) +@login_required +def watermark_video_local(video_id): + """إضافة علامة مائية محلية""" + 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}) + + return jsonify({'status': 'ok', 'watermarked_path': wm_path}) + + except Exception as e: + logger.error(f"Watermark error: {e}") + return jsonify({'error': str(e)}), 500 + +# ---------- مسارات المطور ---------- +@app.route('/developer') +@developer_required +def developer_panel(): + """لوحة تحكم المطور""" + return render_template_string(DEVELOPER_TEMPLATE, session=session) + +@app.route('/api/developer/session/create', methods=['POST']) +@login_required +def create_dev_session(): + """إنشاء جلسة مطور""" + if not session.get('is_developer'): + return jsonify({'error': 'غير مصرح'}), 403 + + token = create_developer_session(session['user_id']) + return jsonify({'session_token': token}) -@app.route('/api/save/', methods=['POST']) +@app.route('/api/developer/execute', methods=['POST']) @login_required -def save(video_id): - user_id = session['user_id'] +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)}) + + return jsonify(result) + +@app.route('/api/developer/plugins', methods=['GET']) +@developer_required +def list_plugins(): + """قائمة الإضافات""" db = get_db() - row = db.execute("SELECT saved FROM interactions WHERE user_id=? AND video_id=?", (user_id, video_id)).fetchone() - if row and row['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 = False + plugins = db.execute('SELECT id, name, is_active, created_at FROM developer_plugins WHERE user_id = ?', + (session['user_id'],)).fetchall() + + return jsonify([dict(p) for p in 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 + + file = request.files['plugin'] + if file.filename == '': + return jsonify({'error': 'لم يتم اختيار ملف'}), 400 + + if not allowed_plugin(file.filename): + return jsonify({'error': 'نوع الملف غير مدعوم'}), 400 + + name = request.form.get('name', '').strip() + if not name: + name = os.path.splitext(file.filename)[0] + + code = file.read().decode('utf-8') + + success = install_plugin(session['user_id'], name, code) + + if success: + track_event(session['user_id'], 'upload_plugin', {'plugin_name': name}) + return jsonify({'status': 'ok', 'name': name}) 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 = True - db.commit() - saves = db.execute("SELECT saves_count FROM videos WHERE id=?", (video_id,)).fetchone()['saves_count'] - return jsonify({'status':'ok', 'saved':saved, 'saves_count':saves}) + return jsonify({'error': 'فشل تثبيت الإضافة'}), 500 -@app.route('/api/share/', methods=['POST']) -@login_required -def share(video_id): - user_id = session['user_id'] +@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) + 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)) - db.execute("UPDATE videos SET shares_count = shares_count + 1 WHERE id=?", (video_id,)) + db.execute('UPDATE developer_plugins SET is_active = ? WHERE id = ? AND user_id = ?', + (is_active, plugin_id, session['user_id'])) db.commit() - add_coins(user_id, 5, 'مشاركة') - add_xp(user_id, 'share', 3) - return jsonify({'status':'ok'}) + + # إعادة تحميل الإضافات + 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/saved') +# ---------- مسارات الألعاب ---------- +@app.route('/api/gamification/xp', methods=['GET']) @login_required -def saved_videos(): +def get_xp(): + """الحصول على نقاط الخبرة""" 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 + 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() - result = [] - for v in videos: - v = dict(v) - v['url'] = v['cloudinary_url'] - v['avatar_url'] = f"/avatars/{v['avatar']}" - result.append(v) - return jsonify(result) + + return jsonify({ + 'xp': user['xp'], + 'level': user['level'], + 'history': [dict(h) for h in history] + }) -# ---------- API: Comments ---------- -@app.route('/api/comments/') +@app.route('/api/gamification/daily', methods=['POST']) @login_required -def get_comments(video_id): +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() - comments = db.execute(''' - SELECT c.*, u.username, u.avatar - 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.timestamp DESC - LIMIT 50 - ''', (video_id,)).fetchall() + users = db.execute(''' + SELECT username, avatar, xp, level, is_verified + FROM users + ORDER BY xp DESC + LIMIT ? + ''', (limit,)).fetchall() + result = [] - for c in comments: - cdict = dict(c) - cdict['avatar_url'] = f"/avatars/{c['avatar']}" - cdict['liked_by_user'] = db.execute("SELECT * FROM comment_likes WHERE user_id=? AND comment_id=?", (session['user_id'], c['id'])).fetchone() is not None - # replies - replies = db.execute(''' - SELECT c.*, u.username, u.avatar - FROM comments c - JOIN users u ON c.user_id = u.id - WHERE c.parent_id=? - ORDER BY c.timestamp ASC - ''', (c['id'],)).fetchall() - cdict['replies'] = [] - for r in replies: - rdict = dict(r) - rdict['avatar_url'] = f"/avatars/{r['avatar']}" - rdict['liked_by_user'] = db.execute("SELECT * FROM comment_likes WHERE user_id=? AND comment_id=?", (session['user_id'], r['id'])).fetchone() is not None - cdict['replies'].append(rdict) - result.append(cdict) + for u in users: + user = dict(u) + user['avatar_url'] = f'/avatars/{u["avatar"]}' + result.append(user) + return jsonify(result) -@app.route('/api/comment/', methods=['POST']) +# ---------- مسارات التحليلات ---------- +@app.route('/api/analytics/stats', methods=['GET']) @login_required -def add_comment(video_id): +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() - text = data.get('comment','').strip() - parent = data.get('parent_id') - if not text: - return jsonify({'error':'لا يمكن أن يكون التعليق فارغاً'}),400 + 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('/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() - cur = db.execute(''' - INSERT INTO comments (user_id, video_id, parent_id, comment_text) - VALUES (?,?,?,?) - ''', (session['user_id'], video_id, parent, text)) - cid = cur.lastrowid - db.execute("UPDATE videos SET comments_count = comments_count + 1 WHERE id=?", (video_id,)) - db.commit() - add_xp(session['user_id'], 'comment', 3) - return jsonify({'status':'ok', 'comment_id':cid}) + 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('/api/comment/like/', methods=['POST']) +@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 like_comment(comment_id): +def notification_stream(): + """بث الإشعارات المباشر""" user_id = session['user_id'] - db = get_db() - if db.execute("SELECT * FROM comment_likes WHERE user_id=? AND comment_id=?", (user_id, comment_id)).fetchone(): - db.execute("DELETE FROM comment_likes WHERE user_id=? AND comment_id=?", (user_id, comment_id)) - db.execute("UPDATE comments SET likes_count = likes_count - 1 WHERE id=?", (comment_id,)) - liked = False - else: - db.execute("INSERT INTO comment_likes (user_id, comment_id) VALUES (?,?)", (user_id, comment_id)) - db.execute("UPDATE comments SET likes_count = likes_count + 1 WHERE id=?", (comment_id,)) - liked = True - # Notify comment owner - comment = db.execute("SELECT user_id FROM comments WHERE id=?", (comment_id,)).fetchone() - if comment and comment['user_id'] != user_id: - username = db.execute("SELECT username FROM users WHERE id=?", (user_id,)).fetchone()['username'] - add_notification(comment['user_id'], user_id, 'like_comment', f'أعجب {username} بتعليقك') - db.commit() - likes = db.execute("SELECT likes_count FROM comments WHERE id=?", (comment_id,)).fetchone()['likes_count'] - return jsonify({'status':'ok', 'liked':liked, 'likes_count':likes}) + + 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'}) -# ---------- Camera upload ---------- -@app.route('/api/camera/upload', methods=['POST']) +@app.route('/api/notifications') @login_required -def camera_upload(): - if 'video' not in request.files: - return jsonify({'error':'لا يوجد فيديو'}),400 - file = request.files['video'] - temp = os.path.join('/tmp', f"cam_{uuid.uuid4().hex}.mp4") - file.save(temp) - url, public_id = upload_video_to_cloudinary(temp) - os.remove(temp) - if not url: - return jsonify({'error':'فشل الرفع'}),500 - db = get_db() - cur = db.execute(''' - INSERT INTO videos (user_id, cloudinary_url, cloudinary_public_id, title, visibility) - VALUES (?,?,?,?,?) - ''', (session['user_id'], url, public_id, 'تسجيل كاميرا', 'public')) - vid = cur.lastrowid - db.commit() - add_xp(session['user_id'], 'upload', 20) - return jsonify({'status':'ok', 'video_id':vid, 'url':url}) +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 -# ---------- Watermark ---------- -@app.route('/api/watermark/', methods=['POST']) +@app.route('/api/notifications/count') @login_required -def watermark(video_id): - db = get_db() - vid = db.execute("SELECT cloudinary_public_id FROM videos WHERE id=?", (video_id,)).fetchone() - if not vid: - return jsonify({'error':'فيديو غير موجود'}),404 - wm_url = watermark_url(vid['cloudinary_public_id'], text="ARC") - return jsonify({'watermarked_url': wm_url}) - -# ---------- Profile API ---------- -@app.route('/api/profile/') +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 profile_api(user_id): - db = get_db() - user = db.execute("SELECT * FROM users WHERE id=?", (user_id,)).fetchone() - if not user: - return jsonify({'error':'غير موجود'}),404 - videos = db.execute(''' - SELECT id, title, cloudinary_url, thumbnail_url, views, likes_count, comments_count - FROM videos WHERE user_id=? AND visibility='public' - ORDER BY upload_time DESC LIMIT 30 - ''', (user_id,)).fetchall() - followers = db.execute("SELECT COUNT(*) FROM follows WHERE user_id=?", (user_id,)).fetchone()[0] - following = db.execute("SELECT COUNT(*) FROM follows WHERE follower_id=?", (user_id,)).fetchone()[0] - is_following = False - if session['user_id'] != user_id: - is_following = db.execute("SELECT * FROM follows WHERE user_id=? AND follower_id=?", (user_id, session['user_id'])).fetchone() is not None - total_views = db.execute("SELECT SUM(views) FROM videos WHERE user_id=?", (user_id,)).fetchone()[0] or 0 - return jsonify({ - 'user': dict(user), - 'videos': [dict(v) for v in videos], - 'followers_count': followers, - 'following_count': following, - 'is_following': is_following, - 'total_views': total_views - }) +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 -# ---------- Glass routes (SPA partials) ---------- -@app.route('/glass/profile/') +@app.route('/api/messages/send/', methods=['POST']) @login_required -def glass_profile(user_id): - return render_template_string(GLASS_PROFILE_TEMPLATE, user_id=user_id, session=session) +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('/glass/saved') +@app.route('/api/conversations') @login_required -def glass_saved(): - return render_template_string(GLASS_SAVED_TEMPLATE) +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('/glass/friends') +# ---------- نظام البث المباشر ---------- +@app.route('/live') @login_required -def glass_friends(): - return render_template_string(GLASS_FRIENDS_TEMPLATE, session=session) +def live_page(): + """صفحة البث المباشر""" + return render_template_string(LIVE_TEMPLATE, session=session) -@app.route('/glass/search') +@app.route('/api/live/start', methods=['POST']) @login_required -def glass_search(): - return render_template_string(GLASS_SEARCH_TEMPLATE) +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], session=session) + +@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) -# ---------- Serve avatars ---------- @app.route('/avatars/') def serve_avatar(filename): - return send_from_directory(AVATAR_FOLDER, filename) + """تقديم الصور الشخصية""" + return send_from_directory(app.config['AVATAR_FOLDER'], filename) -# ---------- HTML Templates (embedded) ---------- +@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, session=session) + +# ---------- قوالب HTML ---------- LOGIN_PAGE = ''' - -تسجيل الدخول - ARC - + + + + تسجيل الدخول - ARC + -
-

ARC

-
-
-
-
- -
-{% if error %}
{{ error }}
{% endif %} - -
+ ''' @@ -997,40 +3595,206 @@ button:hover{background:white;color:#764ba2;} REGISTER_PAGE = ''' - -تسجيل جديد - ARC - + + + + تسجيل جديد - ARC + -
-

ARC

-
-
-
-
-
-
- -
-{% if error %}
{{ error }}
{% endif %} - -
+
+

ARC

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

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

+

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

+
+ + +
+ {% if message %}
{{ message }}
{% endif %} + +
''' @@ -1038,86 +3802,942 @@ button:hover{background:white;color:#764ba2;} UPLOAD_TEMPLATE = ''' - -رفع فيديو - ARC - + + + + رفع فيديو - ARC + -
-

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

-
- -
- - - - -
- -
-
-
-
-
-
-
السماح بالتعليقات
-
📁 اختر فيديو من الجهاز
- -
-العودة للرئيسية -
- +
+

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

+ +
+ + +
+ + + + +
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ السماح بالتعليقات +
+
+ السماح بالثنائي +
+
+ السماح بالمونتاج +
+
+ +
+ + +
+ + +
+ العودة للرئيسية +
+ + + + +''' + +PROFILE_TEMPLATE = ''' + + + + + + @{{ user.username }} - 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 + + + +
+

الإعدادات

+ +
+ + + +
+ + +
+ +
+

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

+ + + + + + + + + + + + + + +
+ +
+

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

+ + + + + +
+ +
+

الإشعارات

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+

التفضيلات

+
+ + +
+ + +
+ + +
+ العودة للرئيسية +
+ + + + +''' + +VIDEO_PAGE_TEMPLATE = ''' + + + + + + ARC - فيديو + + + + ← العودة +
+ +
+ + +''' + +LIVE_TEMPLATE = ''' + + + + + + البث المباشر - ARC + + + + ← العودة للرئيسية +

البث المباشر

+ + + +
+
+
+ + ''' @@ -1126,116 +4746,1247 @@ MAIN_TEMPLATE = ''' - - -ARC - فيديوهات قصيرة - + + + ARC - فيديوهات قصيرة + -
- -
-
❤️
- +
+ +
+
+

💬 التعليقات

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

لوحة الإدارة

+ +
+

المستخدمين

{{ stats.users }}
+

الفيديوهات

{{ stats.videos }}
+

التعليقات

{{ stats.comments }}
+

البلاغات

{{ stats.reports }}
+
+ +

البلاغات

+ + + + {% for r in reports %} + + + + + + + + + {% endfor %} + +
IDالمبلغالمبلغ عليهالسببالحالةالإجراء
{{ r.id }}{{ r.reporter_name }} + {% if r.reported_user_id %}مستخدم: {{ r.reported_name }} + {% elif r.reported_video_id %}فيديو: {{ r.video_name }} + {% elif r.reported_comment_id %}تعليق: {{ r.comment_text }} + {% endif %} + {{ r.reason }}{{ r.status }} + {% if r.status == 'pending' %} +
+
+ {% endif %} +
+
+ + +''' + +DEVELOPER_TEMPLATE = ''' + + + + + + وضع المطور - ARC + + + + ← العودة للرئيسية +
+

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

+ +
+

تنفيذ كود Python

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

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

+
+ + + +
+
+
+
+ + ''' GLASS_PROFILE_TEMPLATE = ''' -

الملف الشخصي

جاري التحميل...
+
+

الملف الشخصي

+
جاري التحميل...
+
''' GLASS_SAVED_TEMPLATE = ''' -

الفيديوهات المحفوظة

+
+

الفيديوهات المحفوظة

+
+
''' GLASS_FRIENDS_TEMPLATE = ''' -

الأصدقاء

+
+

الأصدقاء

+
+
''' GLASS_SEARCH_TEMPLATE = ''' -

بحث

+
+

بحث

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