ARC / app.py
ARASCA's picture
Upload app.py
ca9fff0 verified
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
from typing import Dict, List, Optional, Tuple, Any
from dataclasses import dataclass
from enum import Enum
import hmac
# استيراد Cloudinary
import cloudinary
import cloudinary.uploader
import cloudinary.api
# إنشاء المجلدات الضرورية
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)
os.makedirs('cache', exist_ok=True)
# إعداد التسجيل المتقدم
logging.basicConfig(
filename='logs/app.log',
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# محاولة استيراد المكتبات المتقدمة
try:
from PIL import Image, ImageDraw, ImageFont, ImageFilter
HAS_PIL = True
except ImportError:
HAS_PIL = False
logger.warning("PIL غير مثبت، ميزات الصور محدودة.")
try:
import cv2
import face_recognition
HAS_CV2 = True
HAS_FACE_RECOGNITION = True
except ImportError:
HAS_CV2 = False
HAS_FACE_RECOGNITION = False
logger.warning("OpenCV أو face_recognition غير مثبتين.")
try:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
HAS_CRYPTO = True
except ImportError:
HAS_CRYPTO = False
logger.warning("PyCryptodome غير مثبت، التشفير المتقدم لن يعمل.")
try:
import redis
HAS_REDIS = True
REDIS_CLIENT = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
except ImportError:
HAS_REDIS = False
REDIS_CLIENT = None
logger.warning("Redis غير مثبت، سيتم استخدام الذاكرة المؤقتة المحلية.")
try:
import pyotp
import qrcode
from io import BytesIO
HAS_2FA = True
except ImportError:
HAS_2FA = False
logger.warning("pyotp أو qrcode غير مثبتين، المصادقة الثنائية غير متاحة.")
try:
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
HAS_LIMITER = True
except ImportError:
HAS_LIMITER = False
logger.warning("Flask-Limiter غير مثبت، حماية Rate Limiting غير متاحة.")
from flask import (
Flask, request, session, g, jsonify, render_template_string,
redirect, url_for, abort, send_from_directory, Response,
make_response, flash
)
from werkzeug.utils import secure_filename
from werkzeug.security import generate_password_hash, check_password_hash
# ==================== التكوين الأساسي ====================
cloudinary.config(
cloud_name="dpylnwrw0",
api_key="631276857136451",
api_secret="xpehguQcV_7nj0iBXsMNM5PssHE"
)
app = Flask(__name__)
app.secret_key = secrets.token_hex(64)
app.config['SESSION_TYPE'] = 'filesystem'
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=30)
app.config['JSON_AS_ASCII'] = False
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['REMEMBER_COOKIE_SECURE'] = True
app.config['REMEMBER_COOKIE_HTTPONLY'] = True
# Rate Limiting
if HAS_LIMITER:
limiter = Limiter(
app=app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"],
storage_uri="memory://"
)
else:
# ديكور بديل بسيط
class limiter:
@staticmethod
def limit(limit_string):
def decorator(f):
return f
return decorator
# إعدادات الملفات
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')
CACHE_FOLDER = os.path.join(os.getcwd(), 'cache')
ALLOWED_VIDEO_EXTENSIONS = {'mp4', 'mov', 'avi', 'mkv', 'webm', '3gp', 'm4v'}
ALLOWED_IMAGE_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp'}
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'] = 1024 * 1024 * 1024 # 1GB للفيديوهات 4K
# مفتاح التشفير الرئيسي
MASTER_ENCRYPTION_KEY = hashlib.sha256(app.secret_key.encode()).digest()
# قاعدة البيانات
DATABASE = os.path.join(os.getcwd(), 'arc_video_pro.db')
VECTOR_DIM = 64 # زيادة أبعاد المتجهات للذكاء الاصطناعي
# مخازن مؤقتة
notification_queues = {}
active_streams = {}
cache_storage = {}
trending_cache = {'videos': [], 'hashtags': [], 'updated_at': None}
recommendation_cache = {}
# ==================== نماذج البيانات ====================
class UserRole(Enum):
USER = 'user'
CREATOR = 'creator'
VIP = 'vip'
VIP_GOLD = 'vip_gold'
MODERATOR = 'moderator'
ADMIN = 'admin'
DEVELOPER = 'developer'
class MembershipTier(Enum):
FREE = 'free'
PREMIUM = 'premium'
VIP = 'vip'
VIP_GOLD = 'vip_gold'
@dataclass
class MembershipBenefits:
max_video_size: int = 500 * 1024 * 1024 # 500MB للعادي
max_video_duration: int = 180 # 3 دقائق
can_upload_4k: bool = False
has_gold_badge: bool = False
no_ads: bool = False
can_live_stream: bool = False
priority_support: bool = False
monthly_coins: int = 0
MEMBERSHIP_BENEFITS = {
MembershipTier.FREE: MembershipBenefits(),
MembershipTier.PREMIUM: MembershipBenefits(
max_video_size=1024 * 1024 * 1024,
max_video_duration=600,
can_upload_4k=True,
no_ads=True,
monthly_coins=100
),
MembershipTier.VIP: MembershipBenefits(
max_video_size=2 * 1024 * 1024 * 1024,
max_video_duration=1800,
can_upload_4k=True,
has_gold_badge=True,
no_ads=True,
can_live_stream=True,
priority_support=True,
monthly_coins=500
),
MembershipTier.VIP_GOLD: MembershipBenefits(
max_video_size=5 * 1024 * 1024 * 1024,
max_video_duration=3600,
can_upload_4k=True,
has_gold_badge=True,
no_ads=True,
can_live_stream=True,
priority_support=True,
monthly_coins=2000
)
}
# ==================== دوال قاعدة البيانات المتقدمة ====================
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
db.execute("PRAGMA foreign_keys = ON")
db.execute("PRAGMA journal_mode = WAL")
db.execute("PRAGMA synchronous = NORMAL")
db.execute("PRAGMA cache_size = 10000")
db.execute("PRAGMA temp_store = MEMORY")
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
def query_db(query, args=(), one=False):
"""دالة مساعدة للاستعلامات مع التخزين المؤقت"""
cur = get_db().execute(query, args)
rv = cur.fetchall()
cur.close()
return (rv[0] if rv else None) if one else rv
def init_db():
"""إنشاء قاعدة البيانات المتقدمة"""
with app.app_context():
db = get_db()
cursor = db.cursor()
# جدول المستخدمين المتقدم
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
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,
role TEXT DEFAULT 'user',
membership_tier TEXT DEFAULT 'free',
membership_expires TIMESTAMP,
is_live BOOLEAN DEFAULT 0,
coins INTEGER DEFAULT 100,
diamonds INTEGER DEFAULT 0,
xp INTEGER DEFAULT 0,
level INTEGER DEFAULT 1,
referral_code TEXT UNIQUE,
referred_by INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_active TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- إحصائيات متقدمة
total_views INTEGER DEFAULT 0,
total_likes_received INTEGER DEFAULT 0,
total_comments_received INTEGER DEFAULT 0,
total_followers INTEGER DEFAULT 0,
total_following INTEGER DEFAULT 0,
-- أمان متقدم
two_factor_secret TEXT,
two_factor_enabled BOOLEAN DEFAULT 0,
backup_codes TEXT,
login_attempts INTEGER DEFAULT 0,
locked_until TIMESTAMP,
api_key TEXT UNIQUE,
api_quota INTEGER DEFAULT 1000,
-- إحالات
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)
)
''')
# جدول الجلسات
cursor.execute('''
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
user_id INTEGER NOT NULL,
ip_address TEXT,
user_agent TEXT,
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 user_settings (
user_id INTEGER PRIMARY KEY,
privacy_profile TEXT DEFAULT 'public',
privacy_videos TEXT DEFAULT 'public',
privacy_likes TEXT DEFAULT 'public',
notifications_likes BOOLEAN DEFAULT 1,
notifications_comments BOOLEAN DEFAULT 1,
notifications_follows BOOLEAN DEFAULT 1,
notifications_messages BOOLEAN DEFAULT 1,
notifications_live BOOLEAN DEFAULT 1,
notifications_gifts BOOLEAN DEFAULT 1,
dark_mode BOOLEAN DEFAULT 1,
language TEXT DEFAULT 'ar',
content_language TEXT DEFAULT 'ar',
autoplay BOOLEAN DEFAULT 1,
save_data BOOLEAN DEFAULT 0,
allow_download BOOLEAN DEFAULT 1,
allow_duet BOOLEAN DEFAULT 1,
allow_stitch BOOLEAN DEFAULT 1,
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,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(follower_id) REFERENCES users(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, follower_id)
)
''')
# جدول الفيديوهات المتقدم
cursor.execute('''
CREATE TABLE IF NOT EXISTS videos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
-- الملفات
filename TEXT NOT NULL,
filepath TEXT NOT NULL,
filesize INTEGER DEFAULT 0,
duration INTEGER DEFAULT 0,
-- Cloudinary
cloudinary_url TEXT,
cloudinary_public_id TEXT,
thumbnail TEXT,
-- محتوى
title TEXT,
description TEXT,
music TEXT,
music_id INTEGER,
-- إعدادات
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,
likes_count INTEGER DEFAULT 0,
shares_count INTEGER DEFAULT 0,
saves_count INTEGER DEFAULT 0,
comments_count INTEGER DEFAULT 0,
-- تحليلات متقدمة
avg_watch_time REAL DEFAULT 0,
completion_rate REAL DEFAULT 0,
-- بيانات AI
vector BLOB,
nsfw_score REAL DEFAULT 0.0,
ai_tags TEXT,
processed_for_ai BOOLEAN DEFAULT 0,
-- أمان
encrypted_path TEXT,
encrypted_key BLOB,
watermarked_path TEXT,
-- تحديات
challenge_id INTEGER,
-- تقارير
is_reported BOOLEAN DEFAULT 0,
report_count INTEGER DEFAULT 0,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(challenge_id) REFERENCES challenges(id)
)
''')
# جدول تفاعلات المستخدمين المتقدم
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,
shared BOOLEAN DEFAULT 0,
saved BOOLEAN DEFAULT 0,
reported BOOLEAN DEFAULT 0,
watch_time INTEGER DEFAULT 0,
watch_percentage REAL DEFAULT 0,
completed BOOLEAN DEFAULT 0,
voted_challenge BOOLEAN DEFAULT 0,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(video_id) REFERENCES videos(id) ON DELETE CASCADE,
UNIQUE(user_id, video_id)
)
''')
# جدول التعليقات المتقدم
cursor.execute('''
CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
video_id INTEGER NOT NULL,
parent_id INTEGER,
comment_text TEXT NOT NULL,
likes_count INTEGER DEFAULT 0,
is_pinned BOOLEAN DEFAULT 0,
is_edited BOOLEAN DEFAULT 0,
edit_history TEXT,
is_reported BOOLEAN DEFAULT 0,
report_count INTEGER DEFAULT 0,
is_moderated BOOLEAN DEFAULT 0,
moderation_action TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(video_id) REFERENCES videos(id) ON DELETE CASCADE,
FOREIGN KEY(parent_id) REFERENCES comments(id) ON DELETE CASCADE
)
''')
# جدول مشاهدات الفيديو
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,
watch_percentage REAL DEFAULT 0,
completed BOOLEAN DEFAULT 0,
ip_address TEXT,
user_agent TEXT,
viewed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(video_id) REFERENCES videos(id) ON DELETE CASCADE
)
''')
# جدول الهاشتاغات
cursor.execute('''
CREATE TABLE IF NOT EXISTS hashtags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tag TEXT UNIQUE NOT NULL,
usage_count INTEGER DEFAULT 1,
total_views INTEGER DEFAULT 0,
total_likes INTEGER DEFAULT 0,
trending_score REAL DEFAULT 0,
last_trending_update TIMESTAMP,
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,
FOREIGN KEY(video_id) REFERENCES videos(id) ON DELETE CASCADE,
FOREIGN KEY(hashtag_id) REFERENCES hashtags(id) ON DELETE CASCADE,
PRIMARY KEY (video_id, hashtag_id)
)
''')
# جدول الإشعارات المتقدم
cursor.execute('''
CREATE TABLE IF NOT EXISTS notifications (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
from_user_id INTEGER,
type TEXT NOT NULL,
content TEXT,
video_id INTEGER,
comment_id INTEGER,
gift_id INTEGER,
is_read BOOLEAN DEFAULT 0,
is_seen BOOLEAN DEFAULT 0,
priority INTEGER DEFAULT 0,
action_url TEXT,
image_url TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(from_user_id) REFERENCES users(id) ON DELETE SET NULL,
FOREIGN KEY(video_id) REFERENCES videos(id) ON DELETE CASCADE,
FOREIGN KEY(comment_id) REFERENCES comments(id) ON DELETE CASCADE
)
''')
# جدول الرسائل الخاصة
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,
is_delivered BOOLEAN DEFAULT 0,
is_deleted BOOLEAN DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(sender_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(receiver_id) REFERENCES users(id) ON DELETE CASCADE
)
''')
# جدول المحادثات
cursor.execute('''
CREATE TABLE IF NOT EXISTS conversations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user1_id INTEGER NOT NULL,
user2_id INTEGER NOT NULL,
last_message TEXT,
last_message_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
unread_count_user1 INTEGER DEFAULT 0,
unread_count_user2 INTEGER DEFAULT 0,
FOREIGN KEY(user1_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(user2_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user1_id, user2_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,
prize_diamonds INTEGER DEFAULT 0,
start_date DATE,
end_date DATE,
is_active BOOLEAN DEFAULT 1,
winner_id INTEGER,
total_participants INTEGER DEFAULT 0,
total_votes INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(winner_id) REFERENCES users(id) ON DELETE SET NULL
)
''')
# جدول المشاركات في التحديات
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,
rank INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(challenge_id) REFERENCES challenges(id) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(video_id) REFERENCES videos(id) ON DELETE CASCADE
)
''')
# جدول التصويت في التحديات
cursor.execute('''
CREATE TABLE IF NOT EXISTS challenge_votes (
user_id INTEGER NOT NULL,
participant_id INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(participant_id) REFERENCES challenge_participants(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, participant_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,
description TEXT,
thumbnail TEXT,
viewers INTEGER DEFAULT 0,
peak_viewers INTEGER DEFAULT 0,
total_watch_time INTEGER DEFAULT 0,
likes INTEGER DEFAULT 0,
gifts_value INTEGER DEFAULT 0,
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ended_at TIMESTAMP,
is_active BOOLEAN DEFAULT 1,
recording_url TEXT,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)
''')
# جدول هدايا البث المباشر
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) ON DELETE CASCADE,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)
''')
# جدول الهدايا الافتراضية
cursor.execute('''
CREATE TABLE IF NOT EXISTS virtual_gifts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price INTEGER NOT NULL,
animation_url TEXT,
image_url TEXT,
is_active BOOLEAN DEFAULT 1
)
''')
# جدول نقاط الخبرة
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) ON DELETE CASCADE
)
''')
# جدول المكافآت اليومية
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,
reward_xp INTEGER DEFAULT 0,
streak INTEGER DEFAULT 1,
claimed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, reward_date)
)
''')
# جدول المعاملات المالية
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,
currency TEXT DEFAULT 'coins',
balance_after INTEGER,
description TEXT,
reference_id TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)
''')
# جدول الإحالات
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,
reward_xp INTEGER DEFAULT 20,
status TEXT DEFAULT 'completed',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(referrer_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(referred_id) REFERENCES users(id) ON DELETE CASCADE,
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) ON DELETE CASCADE
)
''')
# جدول البلاغات
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,
details TEXT,
status TEXT DEFAULT 'pending',
action_taken TEXT,
handled_by INTEGER,
handled_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(reporter_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(reported_user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(reported_video_id) REFERENCES videos(id) ON DELETE CASCADE,
FOREIGN KEY(reported_comment_id) REFERENCES comments(id) ON DELETE CASCADE,
FOREIGN KEY(handled_by) REFERENCES users(id) ON DELETE SET NULL
)
''')
# جدول إضافات المطورين
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,
updated_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) ON DELETE CASCADE
)
''')
# جدول أحداث التحليلات
cursor.execute('''
CREATE TABLE IF NOT EXISTS analytics_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
session_id TEXT,
event_name TEXT NOT NULL,
event_data TEXT,
page_url TEXT,
ip_address TEXT,
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# جدول الموسيقى
cursor.execute('''
CREATE TABLE IF NOT EXISTS music_tracks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
artist TEXT,
album TEXT,
duration INTEGER,
url TEXT,
cloudinary_public_id TEXT,
cover_image TEXT,
genre TEXT,
usage_count INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT 1,
uploaded_by INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# جدول كلمات البحث
cursor.execute('''
CREATE TABLE IF NOT EXISTS search_queries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
query TEXT NOT NULL,
results_count INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE SET NULL
)
''')
# إنشاء الفهارس لتحسين الأداء
cursor.execute('CREATE INDEX IF NOT EXISTS idx_videos_user_id ON videos(user_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_videos_upload_time ON videos(upload_time DESC)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_videos_views ON videos(views DESC)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_videos_nsfw ON videos(nsfw_score)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_comments_video_id ON comments(video_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_comments_user_id ON comments(user_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_interactions_user_id ON interactions(user_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_interactions_video_id ON interactions(video_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications(user_id, is_read, created_at DESC)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_follows_user_id ON follows(user_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_follows_follower_id ON follows(follower_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_hashtags_tag ON hashtags(tag)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_hashtags_trending ON hashtags(trending_score DESC)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_video_views_video_id ON video_views(video_id)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_transactions_user_id ON transactions(user_id, created_at DESC)')
db.commit()
# إنشاء البيانات الافتراضية
create_default_data()
logger.info("✅ تم تهيئة قاعدة البيانات بنجاح")
def create_default_data():
"""إنشاء البيانات الافتراضية"""
db = get_db()
# إنشاء مستخدم admin
admin = db.execute("SELECT id FROM users WHERE username = 'admin'").fetchone()
if not admin:
hashed = generate_password_hash('Admin@123456')
referral = generate_referral_code()
cursor = db.execute('''
INSERT INTO users (
username, email, password_hash, role, referral_code,
is_developer, is_verified, coins, diamonds, xp, level
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', ('admin', 'admin@arc.com', hashed, 'admin', referral, 1, 1, 10000, 1000, 10000, 50))
admin_id = cursor.lastrowid
db.execute('INSERT INTO user_settings (user_id) VALUES (?)', (admin_id,))
db.commit()
# إنشاء مستخدم VIP تجريبي
vip = db.execute("SELECT id FROM users WHERE username = 'vip'").fetchone()
if not vip:
hashed = generate_password_hash('Vip@123456')
referral = generate_referral_code()
cursor = db.execute('''
INSERT INTO users (
username, email, password_hash, role, membership_tier,
referral_code, is_verified, coins, diamonds, xp, level
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', ('vip', 'vip@arc.com', hashed, 'vip', 'vip_gold', referral, 1, 5000, 500, 5000, 30))
vip_id = cursor.lastrowid
db.execute('INSERT INTO user_settings (user_id) VALUES (?)', (vip_id,))
db.commit()
# إنشاء مستخدم creator
creator = db.execute("SELECT id FROM users WHERE username = 'creator'").fetchone()
if not creator:
hashed = generate_password_hash('Creator@123456')
referral = generate_referral_code()
cursor = db.execute('''
INSERT INTO users (
username, email, password_hash, role, referral_code,
is_verified, coins, diamonds, xp, level
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', ('creator', 'creator@arc.com', hashed, 'creator', referral, 1, 2000, 100, 2000, 15))
creator_id = cursor.lastrowid
db.execute('INSERT INTO user_settings (user_id) VALUES (?)', (creator_id,))
db.commit()
# إنشاء هدايا افتراضية
gifts = db.execute("SELECT id FROM virtual_gifts").fetchall()
if not gifts:
default_gifts = [
('وردة', 10, '🌹', '/static/gifts/rose.gif'),
('تاج', 50, '👑', '/static/gifts/crown.gif'),
('سيارة', 100, '🚗', '/static/gifts/car.gif'),
('يخت', 500, '🛥️', '/static/gifts/yacht.gif'),
('قصر', 1000, '🏰', '/static/gifts/castle.gif'),
('صاروخ', 2000, '🚀', '/static/gifts/rocket.gif'),
('مجرة', 5000, '🌌', '/static/gifts/galaxy.gif'),
]
for name, price, emoji, url in default_gifts:
db.execute('''
INSERT INTO virtual_gifts (name, price, animation_url, image_url)
VALUES (?, ?, ?, ?)
''', (f'{emoji} {name}', price, url, url))
db.commit()
# إنشاء تحديات افتراضية
challenges = db.execute("SELECT id FROM challenges").fetchall()
if not challenges:
today = datetime.now().strftime('%Y-%m-%d')
next_week = (datetime.now() + timedelta(days=7)).strftime('%Y-%m-%d')
next_month = (datetime.now() + timedelta(days=30)).strftime('%Y-%m-%d')
challenges_data = [
('تحدي الرقص', 'ارقص أفضل رقصة واربح 1000 عملة', 1000, 50, today, next_week),
('تحدي الطبخ', 'اطبخ ألذ طبق في 60 ثانية', 1500, 100, today, next_week),
('تحدي الضحك', 'أضحكنا من قلبك', 500, 20, today, next_week),
('تحدي التقليد', 'قلد أي شخصية مشهورة', 2000, 150, today, next_month),
('تحدي المواهب', 'أظهر موهبتك الفريدة', 3000, 200, today, next_month),
('تحدي الجمال', 'شاركنا روتين جمالك', 800, 30, today, next_week),
('تحدي اللياقة', 'تمرين سريع في 5 دقائق', 1200, 60, today, next_week),
]
for c in challenges_data:
db.execute('''
INSERT INTO challenges (title, description, prize_coins, prize_diamonds, start_date, end_date)
VALUES (?, ?, ?, ?, ?, ?)
''', c)
db.commit()
# إنشاء موسيقى افتراضية
music = db.execute("SELECT id FROM music_tracks").fetchall()
if not music:
music_data = [
('صوت الطبيعة', 'ARC', 'أصوات', 180, None),
('إيقاعات حماسية', 'ARC', 'إلكتروني', 210, None),
('لحن هادئ', 'ARC', 'موسيقى تصويرية', 240, None),
('تحدي الرقص', 'ARC', 'بوب', 195, None),
('مقدمة فيديو', 'ARC', 'إعلاني', 90, None),
]
for title, artist, genre, duration, url in music_data:
db.execute('''
INSERT INTO music_tracks (title, artist, genre, duration, url, is_active)
VALUES (?, ?, ?, ?, ?, 1)
''', (title, artist, genre, duration, url))
db.commit()
# ==================== دوال مساعدة متقدمة ====================
def generate_referral_code():
"""توليد كود إحالة فريد"""
return secrets.token_hex(6).upper()
def random_vector(dim=VECTOR_DIM):
"""توليد متجه عشوائي للفيديوهات الجديدة"""
vec = np.random.randn(dim).astype(np.float32)
return vec.tobytes()
def bytes_to_vector(b):
"""تحويل البيانات الثنائية إلى متجه numpy"""
if b is None:
return np.zeros(VECTOR_DIM, dtype=np.float32)
return np.frombuffer(b, dtype=np.float32)
def allowed_file(filename, allowed_extensions):
"""التحقق من امتداد الملف"""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in allowed_extensions
def allowed_video(filename):
return allowed_file(filename, ALLOWED_VIDEO_EXTENSIONS)
def allowed_image(filename):
return allowed_file(filename, ALLOWED_IMAGE_EXTENSIONS)
def allowed_plugin(filename):
return allowed_file(filename, ALLOWED_PLUGIN_EXTENSIONS)
def get_client_ip():
"""الحصول على عنوان IP العميل"""
if request.headers.get('X-Forwarded-For'):
return request.headers.get('X-Forwarded-For').split(',')[0].strip()
return request.remote_addr
def get_user_membership_tier(user_id):
"""الحصول على مستوى عضوية المستخدم"""
user = query_db('SELECT membership_tier, membership_expires FROM users WHERE id = ?',
(user_id,), one=True)
if not user:
return MembershipTier.FREE
# التحقق من انتهاء الصلاحية
if user['membership_expires']:
expires = datetime.strptime(user['membership_expires'], '%Y-%m-%d %H:%M:%S')
if expires < datetime.now():
return MembershipTier.FREE
try:
return MembershipTier(user['membership_tier'])
except:
return MembershipTier.FREE
def get_membership_benefits(user_id):
"""الحصول على مزايا العضوية للمستخدم"""
tier = get_user_membership_tier(user_id)
return MEMBERSHIP_BENEFITS[tier]
def cache_get(key):
"""الحصول من التخزين المؤقت"""
if HAS_REDIS and REDIS_CLIENT:
return REDIS_CLIENT.get(key)
return cache_storage.get(key)
def cache_set(key, value, timeout=300):
"""تخزين في الذاكرة المؤقتة"""
if HAS_REDIS and REDIS_CLIENT:
REDIS_CLIENT.setex(key, timeout, value)
else:
cache_storage[key] = {
'value': value,
'expires': time.time() + timeout
}
def cache_delete(key):
"""حذف من التخزين المؤقت"""
if HAS_REDIS and REDIS_CLIENT:
REDIS_CLIENT.delete(key)
else:
cache_storage.pop(key, None)
# ==================== دوال الذكاء الاصطناعي المتقدمة ====================
def analyze_video_content(video_path):
"""تحليل متقدم لمحتوى الفيديو"""
results = {
'nsfw_score': 0.0,
'faces_count': 0,
'has_text': False,
'brightness': 0.5,
'motion_score': 0.0,
'scene_type': 'unknown',
'tags': []
}
if not HAS_CV2:
return results
try:
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return results
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
if total_frames == 0:
cap.release()
return results
# أخذ عينات من الفيديو
sample_frames = min(20, total_frames)
frame_indices = [int(i * total_frames / sample_frames) for i in range(sample_frames)]
skin_tone_frames = 0
face_frames = 0
brightness_sum = 0
motion_sum = 0
prev_frame = None
for idx in frame_indices:
cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
ret, frame = cap.read()
if not ret:
continue
# تحليل البشرة
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)
skin_mask = cv2.inRange(hsv, lower_skin, upper_skin)
skin_percentage = (cv2.countNonZero(skin_mask) / (frame.shape[0] * frame.shape[1])) * 100
if skin_percentage > 30:
skin_tone_frames += 1
# اكتشاف الوجوه
if HAS_FACE_RECOGNITION:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
face_locations = face_recognition.face_locations(gray)
if face_locations:
face_frames += len(face_locations)
# تحليل السطوع
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
brightness_sum += np.mean(gray) / 255.0
# تحليل الحركة
if prev_frame is not None:
flow = cv2.calcOpticalFlowFarneback(prev_frame, gray, None, 0.5, 3, 15, 3, 5, 1.2, 0)
motion = np.mean(np.sqrt(flow[...,0]**2 + flow[...,1]**2))
motion_sum += motion
prev_frame = gray
cap.release()
# حساب النتائج
results['nsfw_score'] = skin_tone_frames / sample_frames
results['faces_count'] = face_frames // sample_frames if sample_frames > 0 else 0
results['brightness'] = brightness_sum / sample_frames if sample_frames > 0 else 0.5
results['motion_score'] = motion_sum / (sample_frames - 1) if sample_frames > 1 else 0
# تحديد نوع المشهد
if results['faces_count'] > 2:
results['scene_type'] = 'group'
elif results['faces_count'] > 0:
results['scene_type'] = 'portrait'
elif results['motion_score'] > 10:
results['scene_type'] = 'action'
elif results['motion_score'] < 2:
results['scene_type'] = 'static'
else:
results['scene_type'] = 'normal'
# توليد وسوم تلقائية
tags = []
if results['faces_count'] > 0:
tags.append('بشر')
tags.append('people')
if results['scene_type'] == 'action':
tags.append('حركة')
tags.append('action')
if results['brightness'] > 0.7:
tags.append('مشرق')
tags.append('bright')
elif results['brightness'] < 0.3:
tags.append('داكن')
tags.append('dark')
results['tags'] = tags[:10]
except Exception as e:
logger.error(f"خطأ في تحليل الفيديو: {e}")
return results
def generate_ai_tags(text, video_analysis=None):
"""توليد وسوم ذكية من النص وتحليل الفيديو"""
tags = set()
# استخراج الهاشتاغات
hashtags = re.findall(r'#(\w+)', text)
tags.update(hashtags)
# استخراج الكلمات المهمة
words = re.findall(r'[\w]+', text)
important_words = [w for w in words if len(w) > 2 and w.isalpha()]
common_tags = ['فيديو', 'ترند', 'مشاهير', 'جديد', 'حلويات', 'طبخ',
'comedy', 'funny', 'music', 'dance', 'tiktok', 'fyp',
'trending', 'viral', 'love', 'happy', 'sad', 'emotional']
for word in important_words:
word_lower = word.lower()
if word_lower in common_tags:
tags.add(word_lower)
# إضافة وسوم من تحليل الفيديو
if video_analysis:
tags.update(video_analysis.get('tags', []))
return json.dumps(list(tags)[:20])
def moderate_comment(comment_text):
"""فحص التعليقات تلقائياً"""
# كلمات ممنوعة
banned_words = ['سب', 'شتم', 'سخاف', 'قذر', 'fuck', 'shit', 'ass', 'sex', 'porn']
comment_lower = comment_text.lower()
# فحص الكلمات الممنوعة
for word in banned_words:
if word in comment_lower:
return {
'is_appropriate': False,
'reason': f'يحتوي على كلمة ممنوعة: {word}',
'action': 'block'
}
# فحص الروابط المشبوهة
suspicious_patterns = [r'bit\.ly', r'tinyurl', r'goo\.gl', r'http://', r'https://']
for pattern in suspicious_patterns:
if re.search(pattern, comment_lower):
return {
'is_appropriate': False,
'reason': 'يحتوي على رابط خارجي',
'action': 'flag'
}
# فحص التكرار (سبام)
words = comment_lower.split()
if len(set(words)) < len(words) * 0.5 and len(words) > 10:
return {
'is_appropriate': False,
'reason': 'نسبة تكرار عالية (احتمال سبام)',
'action': 'flag'
}
return {
'is_appropriate': True,
'reason': None,
'action': 'allow'
}
# ==================== نظام التوصيات المتقدم ====================
def get_user_embedding(user_id):
"""توليد تضمين متقدم للمستخدم"""
db = get_db()
# جمع تفاعلات المستخدم
interactions = db.execute('''
SELECT v.vector, i.liked, i.watch_time, i.completed, i.shared, i.saved
FROM videos v
JOIN interactions i ON v.id = i.video_id
WHERE i.user_id = ? AND v.vector IS NOT NULL
ORDER BY i.timestamp DESC
LIMIT 100
''', (user_id,)).fetchall()
if not interactions:
return np.zeros(VECTOR_DIM, dtype=np.float32)
# حساب التضمين المرجح
vectors = []
weights = []
for interaction in interactions:
vec = bytes_to_vector(interaction['vector'])
vectors.append(vec)
# حساب الوزن بناءً على التفاعل
weight = 1.0
if interaction['liked']:
weight += 2.0
if interaction['completed']:
weight += 1.5
if interaction['shared']:
weight += 2.5
if interaction['saved']:
weight += 3.0
# وزن إضافي للمشاهدة الطويلة
watch_time = interaction['watch_time'] or 0
weight += min(watch_time / 60, 2.0) # كل دقيقة مشاهدة تزيد الوزن حتى 2
weights.append(weight)
if not vectors:
return np.zeros(VECTOR_DIM, dtype=np.float32)
# المتوسط المرجح
vectors_array = np.array(vectors)
weights_array = np.array(weights).reshape(-1, 1)
weighted_avg = np.average(vectors_array, axis=0, weights=weights_array.flatten())
return weighted_avg
def get_trending_score(video_id):
"""حساب درجة الشعبية للفيديو"""
db = get_db()
video = db.execute('''
SELECT views, likes_count, comments_count, shares_count, saves_count, upload_time
FROM videos WHERE id = ?
''', (video_id,)).fetchone()
if not video:
return 0
# معاملات الوزن
weights = {
'view': 1,
'like': 3,
'comment': 5,
'share': 8,
'save': 4
}
# حساب النقاط الأساسية
base_score = (
video['views'] * weights['view'] +
video['likes_count'] * weights['like'] +
video['comments_count'] * weights['comment'] +
video['shares_count'] * weights['share'] +
video['saves_count'] * weights['save']
)
# عامل الحداثة (كلما كان الفيديو أحدث، كلما زادت النقاط)
upload_time = datetime.strptime(video['upload_time'], '%Y-%m-%d %H:%M:%S')
hours_ago = (datetime.now() - upload_time).total_seconds() / 3600
freshness = max(0, 100 / (hours_ago + 1))
# عامل الانتشار
total_interactions = video['views'] + video['likes_count'] + video['comments_count']
spread_factor = 1 + (video['shares_count'] / (total_interactions + 1))
final_score = base_score * freshness * spread_factor
return final_score
def update_trending():
"""تحديث المحتوى الرائج"""
global trending_cache
db = get_db()
# تحديث الفيديوهات الرائجة
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.visibility = 'public' AND v.nsfw_score < 0.7
ORDER BY
(v.views * 1 + v.likes_count * 3 + v.comments_count * 5 + v.shares_count * 8 + v.saves_count * 4) *
(1.0 / (julianday('now') - julianday(v.upload_time))) DESC
LIMIT 50
''').fetchall()
# تحديث الهاشتاغات الرائجة
hashtags = db.execute('''
SELECT h.*,
(h.usage_count * 1 + h.total_views * 0.1 + h.total_likes * 0.3) as trending_score
FROM hashtags h
ORDER BY trending_score DESC
LIMIT 20
''').fetchall()
trending_cache = {
'videos': [dict(v) for v in videos],
'hashtags': [dict(h) for h in hashtags],
'updated_at': datetime.now()
}
return trending_cache
def get_trending():
"""الحصول على المحتوى الرائج"""
global trending_cache
# تحديث كل ساعة
if (trending_cache['updated_at'] and
datetime.now() - trending_cache['updated_at'] < timedelta(hours=1)):
return trending_cache
return update_trending()
def recommend_videos_advanced(user_id, limit=10, offset=0, include_explore=False):
"""نظام توصيات متقدم"""
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]
# الحصول على تضمين المستخدم
user_embedding = get_user_embedding(user_id)
# الحصول على الفيديوهات المتاحة
placeholders = ','.join(['?'] * len(watched_ids))
# استعلام متقدم مع عوامل متعددة
query = f'''
SELECT
v.*,
u.username,
u.avatar,
u.is_verified,
u.role,
(SELECT COUNT(*) FROM follows WHERE user_id = v.user_id) as creator_followers,
(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,
julianday('now') - julianday(v.upload_time) as days_ago
FROM videos v
JOIN users u ON v.user_id = u.id
WHERE v.id NOT IN ({placeholders})
AND v.visibility = 'public'
AND v.nsfw_score < 0.7
'''
if not include_explore:
# في الوضع العادي، نفضل الفيديوهات من المستخدمين المشهورين أو المتبعين
query += ' AND (u.role IN ("creator", "vip") OR v.user_id IN ({follow_ph}))'.format(
follow_ph=','.join(['?'] * len(following_ids)) if following_ids else 'NULL'
)
query += ' ORDER BY v.upload_time DESC LIMIT ? OFFSET ?'
params = watched_ids + (following_ids if following_ids else []) + [limit * 3, offset]
rows = db.execute(query, params).fetchall()
video_scores = []
for row in rows:
score = 0
# 1. تشابه المحتوى (30% من النتيجة)
if not np.all(user_embedding == 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(user_embedding - vec))
score += similarity * 30
# 2. الشعبية (25% من النتيجة)
popularity = (
row['views'] * 1 +
row['likes_count'] * 3 +
row['comments_count'] * 5 +
row['shares_count'] * 8
) / 1000
score += min(popularity, 25)
# 3. الحداثة (20% من النتيجة)
days_ago = row['days_ago'] or 0
recency = max(0, 20 - (days_ago * 2))
score += recency
# 4. المتابعة (15% من النتيجة)
if row['user_id'] in following_ids:
score += 15
elif row['creator_followers'] > 1000: # منشئ محتوى مشهور
score += 5
# 5. نسبة التفاعل (10% من النتيجة)
if row['views'] > 0:
engagement = (row['likes_count'] + row['comments_count']) / row['views'] * 100
score += min(engagement, 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"خطأ في نظام التوصيات: {e}")
# fallback إلى فيديوهات عشوائية
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' AND v.nsfw_score < 0.7
ORDER BY RANDOM()
LIMIT ? OFFSET ?
''', (limit, offset)).fetchall()
return [dict(row) for row in rows]
# ==================== نظام النقاط والمستويات المتقدم ====================
def calculate_level_from_xp(xp):
"""حساب المستوى من نقاط الخبرة (منحنى أسي)"""
if xp < 100:
return 1
return int((xp / 100) ** 0.5) + 1
def calculate_xp_for_level(level):
"""حساب النقاط المطلوبة لمستوى معين"""
return 100 * (level - 1) ** 2
def add_xp(user_id, action, base_xp, multiplier=1.0):
"""إضافة نقاط خبرة مع مضاعفات"""
db = get_db()
try:
# حساب النقاط الفعلية مع المضاعفات
xp_gained = int(base_xp * multiplier)
db.execute('''
INSERT INTO user_xp (user_id, action, xp_gained)
VALUES (?, ?, ?)
''', (user_id, action, xp_gained))
db.execute('UPDATE users SET xp = xp + ? WHERE id = ?', (xp_gained, user_id))
# تحديث المستوى
user = db.execute('SELECT xp, level FROM users WHERE id = ?', (user_id,)).fetchone()
if user:
new_level = calculate_level_from_xp(user['xp'])
if new_level > user['level']:
db.execute('UPDATE users SET level = ? WHERE id = ?', (new_level, user_id))
# مكافأة عند زيادة المستوى
add_coins(user_id, new_level * 10, f'مكافأة وصول للمستوى {new_level}')
# إشعار بزيادة المستوى
add_notification(
user_id, None, 'level_up',
f'تهانينا! وصلت إلى المستوى {new_level}',
priority=2
)
db.commit()
return True
except Exception as e:
logger.error(f"خطأ في إضافة XP: {e}")
return False
def add_coins(user_id, amount, description, currency='coins'):
"""إضافة عملات مع تسجيل المعاملة"""
db = get_db()
try:
# الحصول على الرصيد الحالي
user = db.execute(f'SELECT {currency} FROM users WHERE id = ?', (user_id,)).fetchone()
balance_before = user[currency] if user else 0
# تحديث الرصيد
db.execute(f'UPDATE users SET {currency} = {currency} + ? WHERE id = ?', (amount, user_id))
# تسجيل المعاملة
db.execute('''
INSERT INTO transactions (user_id, type, amount, currency, balance_after, description)
VALUES (?, 'earn', ?, ?, ?, ?)
''', (user_id, amount, currency, balance_before + amount, description))
db.commit()
return True
except Exception as e:
logger.error(f"خطأ في إضافة العملات: {e}")
return False
def deduct_coins(user_id, amount, description, currency='coins'):
"""خصم عملات مع تسجيل المعاملة"""
db = get_db()
try:
# التحقق من الرصيد
user = db.execute(f'SELECT {currency} FROM users WHERE id = ?', (user_id,)).fetchone()
if not user or user[currency] < amount:
return False
balance_before = user[currency]
# تحديث الرصيد
db.execute(f'UPDATE users SET {currency} = {currency} - ? WHERE id = ?', (amount, user_id))
# تسجيل المعاملة
db.execute('''
INSERT INTO transactions (user_id, type, amount, currency, balance_after, description)
VALUES (?, 'spend', ?, ?, ?, ?)
''', (user_id, amount, currency, balance_before - amount, description))
db.commit()
return True
except Exception as e:
logger.error(f"خطأ في خصم العملات: {e}")
return False
def claim_daily_reward(user_id):
"""المطالبة بالمكافأة اليومية المتقدمة"""
db = get_db()
today = datetime.now().date()
try:
# التحقق من استلام المكافأة اليوم
existing = db.execute('''
SELECT id, streak FROM daily_rewards
WHERE user_id = ? AND reward_date = ?
''', (user_id, today.isoformat())).fetchone()
if existing:
return False, "تم استلام المكافأة اليوم مسبقاً"
# حساب السلسلة المتتالية
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
# حساب المكافآت (تزيد مع السلسلة)
base_coins = 50
base_xp = 20
coins = base_coins * streak
xp = base_xp * streak
# مكافآت إضافية للسلاسل الطويلة
bonus_coins = 0
if streak >= 7:
bonus_coins += 100 # مكافأة أسبوعية
if streak >= 30:
bonus_coins += 500 # مكافأة شهرية
total_coins = coins + bonus_coins
# تسجيل المكافأة
db.execute('''
INSERT INTO daily_rewards (user_id, reward_date, reward_coins, reward_xp, streak)
VALUES (?, ?, ?, ?, ?)
''', (user_id, today.isoformat(), total_coins, xp, streak))
# إضافة العملات والنقاط
add_coins(user_id, total_coins, f'مكافأة يومية (السلسلة: {streak})')
add_xp(user_id, 'daily_reward', xp)
db.commit()
return True, total_coins, xp, streak
except Exception as e:
logger.error(f"خطأ في المكافأة اليومية: {e}")
return False, str(e)
# ==================== الأمن المتقدم ====================
def generate_2fa_secret():
"""توليد سر للمصادقة الثنائية"""
if not HAS_2FA:
return None
return pyotp.random_base32()
def generate_2fa_qr(username, secret):
"""توليد رمز QR للمصادقة الثنائية"""
if not HAS_2FA:
return None
totp = pyotp.TOTP(secret)
provisioning_uri = totp.provisioning_uri(name=username, issuer_name="ARC Video")
# توليد QR code
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(provisioning_uri)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# تحويل إلى base64
buffered = BytesIO()
img.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
return img_str
def verify_2fa_code(secret, code):
"""التحقق من رمز المصادقة الثنائية"""
if not HAS_2FA or not secret:
return False
totp = pyotp.TOTP(secret)
return totp.verify(code)
def generate_backup_codes():
"""توليد رموز احتياطية"""
codes = []
for _ in range(8):
code = secrets.token_hex(4).upper()
codes.append(code)
return json.dumps(codes)
def encrypt_sensitive_data(data, key=None):
"""تشفير البيانات الحساسة"""
if not HAS_CRYPTO:
return data
if key is None:
key = MASTER_ENCRYPTION_KEY
try:
if isinstance(data, str):
data = data.encode('utf-8')
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(data)
return {
'ciphertext': base64.b64encode(ciphertext).decode('utf-8'),
'nonce': base64.b64encode(cipher.nonce).decode('utf-8'),
'tag': base64.b64encode(tag).decode('utf-8')
}
except Exception as e:
logger.error(f"خطأ في التشفير: {e}")
return data
def decrypt_sensitive_data(encrypted_data, key=None):
"""فك تشفير البيانات الحساسة"""
if not HAS_CRYPTO:
return encrypted_data
if key is None:
key = MASTER_ENCRYPTION_KEY
try:
if isinstance(encrypted_data, dict) and 'ciphertext' in encrypted_data:
ciphertext = base64.b64decode(encrypted_data['ciphertext'])
nonce = base64.b64decode(encrypted_data['nonce'])
tag = base64.b64decode(encrypted_data['tag'])
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
data = cipher.decrypt_and_verify(ciphertext, tag)
return data.decode('utf-8')
except Exception as e:
logger.error(f"خطأ في فك التشفير: {e}")
return encrypted_data
# ==================== نظام الإشعارات المتقدم ====================
def add_notification(user_id, from_user_id, n_type, content,
video_id=None, comment_id=None, gift_id=None,
priority=0, action_url=None, image_url=None):
"""إضافة إشعار متقدم"""
if user_id == from_user_id:
return
db = get_db()
# تحديد تاريخ انتهاء الصلاحية (30 يوم)
expires_at = datetime.now() + timedelta(days=30)
cursor = db.execute('''
INSERT INTO notifications (
user_id, from_user_id, type, content, video_id,
comment_id, gift_id, priority, action_url, image_url, expires_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (user_id, from_user_id, n_type, content, video_id,
comment_id, gift_id, priority, action_url, image_url, expires_at))
db.commit()
# إرسال إشعار فوري إذا كان متصلاً
if user_id in notification_queues:
notification_queues[user_id].put({
'id': cursor.lastrowid,
'type': n_type,
'content': content,
'from_user': from_user_id,
'video_id': video_id,
'priority': priority,
'image_url': image_url,
'timestamp': datetime.now().isoformat()
})
return cursor.lastrowid
def send_bulk_notifications(user_ids, from_user_id, n_type, content, **kwargs):
"""إرسال إشعارات جماعية"""
for user_id in user_ids:
add_notification(user_id, from_user_id, n_type, content, **kwargs)
# ==================== دوال المطورين ====================
def create_developer_session(user_id):
"""إنشاء جلسة مطور جديدة"""
token = secrets.token_urlsafe(48)
expires = datetime.now() + timedelta(hours=2)
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 > datetime('now')
''', (token,)).fetchone()
if session:
return session['user_id']
return None
# ==================== الديكوراتورات ====================
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'))
user = query_db('SELECT role FROM users WHERE id = ?', (session['user_id'],), one=True)
if not user or user['role'] not in ['admin', 'moderator']:
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'))
user = query_db('SELECT is_developer FROM users WHERE id = ?', (session['user_id'],), one=True)
if not user or not user['is_developer']:
abort(403)
return f(*args, **kwargs)
return decorated_function
def vip_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user_id' not in session:
return redirect(url_for('login'))
tier = get_user_membership_tier(session['user_id'])
if tier in [MembershipTier.VIP, MembershipTier.VIP_GOLD]:
return f(*args, **kwargs)
abort(403)
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'])
@limiter.limit("10 per minute")
def login():
if request.method == 'POST':
username = request.form.get('username', '').strip()
password = request.form.get('password', '')
remember = request.form.get('remember', False)
twofa_code = request.form.get('twofa_code', '')
db = get_db()
user = db.execute('SELECT * FROM users WHERE username = ? OR email = ?',
(username, username)).fetchone()
if not user:
return render_template_string(LOGIN_PAGE, error='بيانات الدخول غير صحيحة')
# التحقق من قفل الحساب
if user['locked_until']:
locked_until = datetime.strptime(user['locked_until'], '%Y-%m-%d %H:%M:%S')
if locked_until > datetime.now():
return render_template_string(LOGIN_PAGE,
error=f'الحساب مقفل حتى {locked_until.strftime("%H:%M")}')
if check_password_hash(user['password_hash'], password):
# التحقق من المصادقة الثنائية
if user['two_factor_enabled'] and not twofa_code:
return render_template_string(TWOFA_PAGE, user_id=user['id'])
if user['two_factor_enabled']:
if not verify_2fa_code(user['two_factor_secret'], twofa_code):
return render_template_string(LOGIN_PAGE, error='رمز المصادقة غير صحيح')
# إعادة تعيين محاولات الدخول
db.execute('UPDATE users SET login_attempts = 0, locked_until = NULL WHERE id = ?',
(user['id'],))
# إنشاء جلسة
session_id = secrets.token_urlsafe(32)
session['user_id'] = user['id']
session['username'] = user['username']
session['role'] = user['role']
session['session_id'] = session_id
if remember:
session.permanent = True
# تسجيل الجلسة في قاعدة البيانات
db.execute('''
INSERT INTO sessions (id, user_id, ip_address, user_agent, expires_at)
VALUES (?, ?, ?, ?, datetime("now", "+30 days"))
''', (session_id, user['id'], get_client_ip(), request.headers.get('User-Agent', '')))
db.execute('UPDATE users SET last_active = CURRENT_TIMESTAMP WHERE id = ?', (user['id'],))
db.commit()
# XP لتسجيل الدخول اليومي
cache_key = f"login_{user['id']}_{datetime.now().date()}"
if not cache_get(cache_key):
add_xp(user['id'], 'daily_login', 10)
cache_set(cache_key, '1', 86400) # تخزين لمدة 24 ساعة
track_event(user['id'], 'login', {})
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({'success': True, 'redirect': '/'})
return redirect(url_for('index'))
# تسجيل محاولة فاشلة
attempts = user['login_attempts'] + 1
locked_until = None
if attempts >= 5:
locked_until = datetime.now() + timedelta(minutes=15)
db.execute('UPDATE users SET login_attempts = ?, locked_until = ? WHERE id = ?',
(attempts, locked_until, user['id']))
db.commit()
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'])
@limiter.limit("5 per hour")
def register():
if request.method == 'POST':
username = request.form.get('username', '').strip()
email = request.form.get('email', '').strip().lower()
password = request.form.get('password', '')
confirm = request.form.get('confirm_password', '')
referral = request.form.get('referral_code', '').strip()
agree_terms = request.form.get('agree_terms') == 'on'
if not username or not email or not password:
return render_template_string(REGISTER_PAGE, error='جميع الحقول مطلوبة')
if not agree_terms:
return render_template_string(REGISTER_PAGE, error='يجب الموافقة على الشروط والأحكام')
if password != confirm:
return render_template_string(REGISTER_PAGE, error='كلمات المرور غير متطابقة')
if len(password) < 8:
return render_template_string(REGISTER_PAGE,
error='كلمة المرور يجب أن تكون 8 أحرف على الأقل')
if not re.search(r'[A-Z]', password):
return render_template_string(REGISTER_PAGE,
error='كلمة المرور يجب أن تحتوي على حرف كبير واحد على الأقل')
if not re.search(r'[a-z]', password):
return render_template_string(REGISTER_PAGE,
error='كلمة المرور يجب أن تحتوي على حرف صغير واحد على الأقل')
if not re.search(r'[0-9]', password):
return render_template_string(REGISTER_PAGE,
error='كلمة المرور يجب أن تحتوي على رقم واحد على الأقل')
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()
cursor = db.execute('''
INSERT INTO users (username, email, password_hash, referral_code)
VALUES (?, ?, ?, ?)
''', (username, email, hashed, referral_code))
user_id = cursor.lastrowid
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)
# عملات ترحيبية
add_coins(user_id, 100, 'هدية ترحيبية')
track_event(user_id, 'register', {'referral': referral if referral else None})
db.commit()
# إرسال بريد ترحيبي (محاكاة)
logger.info(f"مستخدم جديد: {username} (ID: {user_id})")
return redirect(url_for('login'))
return render_template_string(REGISTER_PAGE)
@app.route('/logout')
def logout():
if 'user_id' in session and 'session_id' in session:
db = get_db()
db.execute('DELETE FROM sessions WHERE id = ?', (session['session_id'],))
db.commit()
track_event(session['user_id'], 'logout', {})
session.clear()
return redirect(url_for('login'))
@app.route('/2fa/setup', methods=['GET', 'POST'])
@login_required
def setup_2fa():
"""إعداد المصادقة الثنائية"""
if not HAS_2FA:
return "المصادقة الثنائية غير متاحة", 501
db = get_db()
user = db.execute('SELECT username, two_factor_secret FROM users WHERE id = ?',
(session['user_id'],)).fetchone()
if request.method == 'POST':
code = request.form.get('code', '')
if verify_2fa_code(user['two_factor_secret'], code):
db.execute('UPDATE users SET two_factor_enabled = 1 WHERE id = ?',
(session['user_id'],))
db.commit()
return redirect(url_for('settings'))
else:
return render_template_string(SETUP_2FA_PAGE, error='رمز غير صحيح')
# توليد سر جديد إذا لم يكن موجوداً
secret = user['two_factor_secret']
if not secret:
secret = generate_2fa_secret()
db.execute('UPDATE users SET two_factor_secret = ? WHERE id = ?',
(secret, session['user_id']))
db.commit()
qr_code = generate_2fa_qr(user['username'], secret)
backup_codes = generate_backup_codes()
db.execute('UPDATE users SET backup_codes = ? WHERE id = ?',
(backup_codes, session['user_id']))
db.commit()
return render_template_string(SETUP_2FA_PAGE,
secret=secret,
qr_code=qr_code,
backup_codes=json.loads(backup_codes))
# ==================== ملف المستخدم ====================
@app.route('/profile/<int:user_id>')
@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)
# الحصول على إحصائيات متقدمة
stats = get_user_stats(user_id)
# فيديوهات المستخدم
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
# تجهيز قائمة الفيديوهات
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)
# مزايا العضوية
benefits = get_membership_benefits(user_id)
return render_template_string(PROFILE_TEMPLATE,
user=dict(user),
videos=videos_list,
stats=stats,
followers_count=followers_count,
following_count=following_count,
is_following=is_following,
benefits=benefits,
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']}_{uuid.uuid4().hex[:8]}.{ext}"
filepath = os.path.join(AVATAR_FOLDER, filename)
avatar.save(filepath)
# تحسين الصورة إذا كان PIL متاحاً
if HAS_PIL:
try:
img = Image.open(filepath)
img = img.resize((400, 400), Image.Resampling.LANCZOS)
img.save(filepath, optimize=True, quality=85)
except:
pass
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']}_{uuid.uuid4().hex[:8]}.{ext}"
filepath = os.path.join(AVATAR_FOLDER, filename)
cover.save(filepath)
if HAS_PIL:
try:
img = Image.open(filepath)
img = img.resize((1500, 500), Image.Resampling.LANCZOS)
img.save(filepath, optimize=True, quality=85)
except:
pass
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')
privacy_likes = request.form.get('privacy_likes', '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'
notifications_live = request.form.get('notifications_live') == 'on'
notifications_gifts = request.form.get('notifications_gifts') == 'on'
# تفضيلات
dark_mode = request.form.get('dark_mode') == 'on'
language = request.form.get('language', 'ar')
content_language = request.form.get('content_language', 'ar')
autoplay = request.form.get('autoplay') == 'on'
save_data = request.form.get('save_data') == 'on'
allow_download = request.form.get('allow_download') == 'on'
allow_duet = request.form.get('allow_duet') == 'on'
allow_stitch = request.form.get('allow_stitch') == 'on'
db.execute('''
UPDATE user_settings SET
privacy_profile = ?, privacy_videos = ?, privacy_likes = ?,
notifications_likes = ?, notifications_comments = ?,
notifications_follows = ?, notifications_messages = ?,
notifications_live = ?, notifications_gifts = ?,
dark_mode = ?, language = ?, content_language = ?,
autoplay = ?, save_data = ?, allow_download = ?,
allow_duet = ?, allow_stitch = ?
WHERE user_id = ?
''', (privacy_profile, privacy_videos, privacy_likes,
notifications_likes, notifications_comments,
notifications_follows, notifications_messages,
notifications_live, notifications_gifts,
dark_mode, language, content_language,
autoplay, save_data, allow_download,
allow_duet, allow_stitch, session['user_id']))
db.commit()
track_event(session['user_id'], 'update_settings', {})
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 {})
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
total_shares = db.execute('SELECT SUM(shares_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()
# إحصائيات آخر 7 أيام
weekly_views = db.execute('''
SELECT SUM(watch_time) FROM video_views
WHERE user_id = ? AND viewed_at > datetime("now", "-7 days")
''', (user_id,)).fetchone()[0] or 0
# إنجازات
achievements = []
if videos >= 10:
achievements.append('منشئ محتوى')
if followers >= 1000:
achievements.append('مؤثر')
if total_views >= 100000:
achievements.append('نجم')
return {
'videos': videos,
'total_views': total_views,
'total_likes': total_likes,
'total_comments': total_comments,
'total_shares': total_shares,
'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'],
'weekly_views': weekly_views,
'achievements': achievements
}
except Exception as e:
logger.error(f"خطأ في إحصائيات المستخدم: {e}")
return {}
# ==================== نظام المتابعة ====================
@app.route('/follow/<int:user_id>', methods=['POST'])
@login_required
@limiter.limit("30 per minute")
def follow(user_id):
if user_id == session['user_id']:
return jsonify({'error': 'لا يمكنك متابعة نفسك'}), 400
db = get_db()
# التحقق من الخصوصية
target_user = db.execute('SELECT username FROM users WHERE id = ?', (user_id,)).fetchone()
if not target_user:
return jsonify({'error': 'المستخدم غير موجود'}), 404
db.execute('INSERT OR IGNORE INTO follows (user_id, follower_id) VALUES (?, ?)',
(user_id, session['user_id']))
# تحديث إحصائيات المتابعين
db.execute('''
UPDATE users SET total_followers = (
SELECT COUNT(*) FROM follows WHERE user_id = ?
) WHERE id = ?
''', (user_id, user_id))
db.execute('''
UPDATE users SET total_following = (
SELECT COUNT(*) FROM follows WHERE follower_id = ?
) WHERE id = ?
''', (session['user_id'], session['user_id']))
db.commit()
# إرسال إشعار
follower = db.execute('SELECT username FROM users WHERE id = ?',
(session['user_id'],)).fetchone()
add_notification(user_id, session['user_id'], 'follow',
f'بدأ {follower["username"]} بمتابعتك',
priority=1)
# XP للمتابعة
add_xp(session['user_id'], 'follow', 5)
# إحصائيات
track_event(session['user_id'], 'follow', {'followed_user_id': user_id})
count = db.execute('SELECT COUNT(*) FROM follows WHERE user_id = ?',
(user_id,)).fetchone()[0]
return jsonify({'status': 'ok', 'followers_count': count})
@app.route('/unfollow/<int:user_id>', 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('''
UPDATE users SET total_followers = (
SELECT COUNT(*) FROM follows WHERE user_id = ?
) WHERE id = ?
''', (user_id, user_id))
db.execute('''
UPDATE users SET total_following = (
SELECT COUNT(*) FROM follows WHERE follower_id = ?
) WHERE id = ?
''', (session['user_id'], session['user_id']))
db.commit()
track_event(session['user_id'], 'unfollow', {'unfollowed_user_id': user_id})
count = db.execute('SELECT COUNT(*) FROM follows WHERE user_id = ?',
(user_id,)).fetchone()[0]
return jsonify({'status': 'ok', 'followers_count': count})
@app.route('/api/followers/<int:user_id>')
@login_required
def get_followers(user_id):
page = int(request.args.get('page', 1))
limit = 20
offset = (page - 1) * limit
db = get_db()
followers = db.execute('''
SELECT u.id, u.username, u.avatar, u.is_verified, u.role,
f.created_at
FROM follows f
JOIN users u ON f.follower_id = u.id
WHERE f.user_id = ?
ORDER BY f.created_at DESC
LIMIT ? OFFSET ?
''', (user_id, limit, offset)).fetchall()
result = []
for f in followers:
follower = dict(f)
follower['avatar_url'] = f'/avatars/{f["avatar"]}'
# هل يتابع المستخدم الحالي هذا الشخص
if session['user_id'] != f['id']:
is_following_back = db.execute('''
SELECT * FROM follows WHERE user_id = ? AND follower_id = ?
''', (f['id'], session['user_id'])).fetchone()
follower['is_following_back'] = is_following_back is not None
else:
follower['is_following_back'] = False
result.append(follower)
return jsonify({
'followers': result,
'has_more': len(result) == limit
})
@app.route('/api/following/<int:user_id>')
@login_required
def get_following(user_id):
page = int(request.args.get('page', 1))
limit = 20
offset = (page - 1) * limit
db = get_db()
following = db.execute('''
SELECT u.id, u.username, u.avatar, u.is_verified, u.role,
f.created_at
FROM follows f
JOIN users u ON f.user_id = u.id
WHERE f.follower_id = ?
ORDER BY f.created_at DESC
LIMIT ? OFFSET ?
''', (user_id, limit, offset)).fetchall()
result = []
for f in following:
follow = dict(f)
follow['avatar_url'] = f'/avatars/{f["avatar"]}'
result.append(follow)
return jsonify({
'following': result,
'has_more': len(result) == limit
})
# ==================== نظام الفيديوهات المتقدم ====================
@app.route('/upload', methods=['GET', 'POST'])
@login_required
def upload_video():
if request.method == 'GET':
db = get_db()
challenges = db.execute('''
SELECT * FROM challenges
WHERE is_active = 1 AND end_date > DATE()
ORDER BY prize_coins DESC
''').fetchall()
music = db.execute('''
SELECT * FROM music_tracks WHERE is_active = 1
ORDER BY usage_count DESC
LIMIT 50
''').fetchall()
return render_template_string(UPLOAD_TEMPLATE,
challenges=[dict(c) for c in challenges],
music=[dict(m) for m in music])
# معالجة رفع الفيديو
if 'video' not in request.files:
return jsonify({'error': 'لم يتم اختيار فيديو'}), 400
file = request.files['video']
if file.filename == '':
return jsonify({'error': 'لم يتم اختيار فيديو'}), 400
# التحقق من مزايا العضوية
benefits = get_membership_benefits(session['user_id'])
# التحقق من حجم الملف
file.seek(0, os.SEEK_END)
file_size = file.tell()
file.seek(0)
if file_size > benefits.max_video_size:
max_size_mb = benefits.max_video_size / (1024 * 1024)
return jsonify({'error': f'حجم الملف يتجاوز الحد المسموح ({max_size_mb}MB)'}), 400
title = request.form.get('title', '')
description = request.form.get('description', '')
music_id = request.form.get('music_id')
challenge_id = request.form.get('challenge_id')
allow_comments = request.form.get('allow_comments') == 'on'
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
# تحليل متقدم للفيديو
video_analysis = analyze_video_content(filepath)
# استخراج الخصائص
if HAS_CV2:
vec_bytes = extract_video_features(filepath)
else:
vec_bytes = random_vector()
# توليد وسوم ذكية
ai_tags = generate_ai_tags(f"{title} {description}", video_analysis)
# تشفير الفيديو إذا كان المستخدم VIP
encrypted_path, enc_key = None, None
if HAS_CRYPTO and benefits.priority_support:
encrypted_path, enc_key = encrypt_video_file(filepath)
# إضافة علامة مائية
watermarked_path = None
if HAS_CV2 and HAS_PIL:
watermark_text = f"@{session['username']}"
watermarked_path = add_watermark_to_video(filepath, watermark_text=watermark_text)
db = get_db()
cursor = db.execute('''
INSERT INTO videos (
user_id, filename, filepath, filesize, cloudinary_url,
cloudinary_public_id, thumbnail, title, description, music_id,
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, file_size,
cloudinary_url, cloudinary_public_id, thumbnail_url,
title, description, music_id,
allow_comments, allow_duet, allow_stitch, visibility,
vec_bytes, challenge_id, video_analysis['nsfw_score'],
ai_tags, encrypted_path, enc_key, 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'], 20, 'مكافأة نشر فيديو')
add_xp(session['user_id'], 'upload_video', 50,
multiplier=2 if benefits.priority_support else 1)
track_event(session['user_id'], 'upload_video', {
'video_id': video_id,
'size': file_size,
'duration': video_analysis.get('duration', 0)
})
return redirect(url_for('index'))
return jsonify({'error': 'نوع الملف غير مدعوم'}), 400
@app.route('/api/for-you')
@login_required
def for_you():
"""الصفحة الرئيسية مع توصيات متقدمة"""
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
# استخدام نظام التوصيات المتقدم
videos = recommend_videos_advanced(session['user_id'], limit, offset)
result = []
for v in videos:
video = {}
for key, value in v.items():
if key not in ['vector', 'encrypted_key'] and not isinstance(value, bytes):
video[key] = value
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, saved 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
video['saved_by_user'] = bool(interaction['saved']) 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,
'has_more': len(result) == limit
})
except Exception as e:
logger.error(f"خطأ في for_you: {e}")
return jsonify({'videos': [], 'next_page': None, 'error': str(e)}), 500
@app.route('/api/trending')
@login_required
def trending():
"""الفيديوهات الرائجة"""
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
trending_data = get_trending()
videos = trending_data['videos'][offset:offset+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)
video.pop('encrypted_key', None)
result.append(video)
return jsonify({
'videos': result,
'next_page': page + 1 if len(result) == limit else None,
'has_more': len(videos) == limit
})
except Exception as e:
logger.error(f"خطأ في trending: {e}")
return jsonify({'videos': [], 'next_page': None}), 500
@app.route('/api/trending/hashtags')
@login_required
def trending_hashtags():
"""الهاشتاغات الرائجة"""
try:
trending_data = get_trending()
return jsonify({'hashtags': trending_data['hashtags']})
except Exception as e:
logger.error(f"خطأ في trending_hashtags: {e}")
return jsonify({'hashtags': []}), 500
@app.route('/api/search')
@login_required
def search():
"""بحث متقدم"""
try:
q = request.args.get('q', '').strip()
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 20))
offset = (page - 1) * limit
content_type = request.args.get('type', 'videos') # videos, users, hashtags
db = get_db()
# تسجيل عملية البحث
if q:
db.execute('INSERT INTO search_queries (user_id, query) VALUES (?, ?)',
(session['user_id'], q))
db.commit()
if content_type == 'videos':
# بحث في الفيديوهات
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'
AND (v.title LIKE ? OR v.description LIKE ?)
ORDER BY v.views DESC, v.likes_count DESC
LIMIT ? OFFSET ?
''', ('%'+q+'%', '%'+q+'%', limit, offset)).fetchall()
# بحث في الهاشتاغات
hashtag_videos = []
hashtag = db.execute('SELECT id FROM hashtags WHERE tag LIKE ?',
('%'+q+'%',)).fetchone()
if hashtag:
hashtag_videos = db.execute('''
SELECT v.*, u.username, u.avatar, u.is_verified
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 ?
''', (hashtag['id'], limit)).fetchall()
# دمج النتائج وإزالة التكرار
all_videos = list(videos) + list(hashtag_videos)
seen = set()
unique_videos = []
for v in all_videos:
if v['id'] not in seen:
seen.add(v['id'])
unique_videos.append(v)
result = []
for v in unique_videos[:limit]:
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,
'next_page': page + 1 if len(result) == limit else None,
'has_more': len(result) == limit
})
elif content_type == 'users':
# بحث عن المستخدمين
users = db.execute('''
SELECT id, username, avatar, bio, is_verified, role,
total_followers
FROM users
WHERE username LIKE ? OR bio LIKE ?
ORDER BY total_followers DESC
LIMIT ? OFFSET ?
''', ('%'+q+'%', '%'+q+'%', limit, offset)).fetchall()
result = []
for u in users:
user = dict(u)
user['avatar_url'] = f'/avatars/{u["avatar"]}'
# هل يتابع المستخدم الحالي هذا الحساب
if session['user_id'] != u['id']:
is_following = db.execute('''
SELECT * FROM follows WHERE user_id = ? AND follower_id = ?
''', (u['id'], session['user_id'])).fetchone()
user['is_following'] = is_following is not None
result.append(user)
return jsonify({
'users': result,
'next_page': page + 1 if len(result) == limit else None
})
elif content_type == 'hashtags':
# بحث عن الهاشتاغات
hashtags = db.execute('''
SELECT tag, usage_count, total_views
FROM hashtags
WHERE tag LIKE ?
ORDER BY usage_count DESC, total_views DESC
LIMIT ? OFFSET ?
''', ('%'+q+'%', limit, offset)).fetchall()
return jsonify({
'hashtags': [dict(h) for h in hashtags],
'next_page': page + 1 if len(hashtags) == limit else None
})
return jsonify({'error': 'نوع بحث غير صحيح'}), 400
except Exception as e:
logger.error(f"خطأ في البحث: {e}")
return jsonify({'videos': []}), 500
@app.route('/api/like/<int:video_id>', methods=['POST'])
@login_required
@limiter.limit("30 per minute")
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=video_id,
priority=1)
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"خطأ في like_video: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/view/<int:video_id>', methods=['POST'])
@login_required
def view_video(video_id):
"""تسجيل مشاهدة متقدمة"""
try:
user_id = session['user_id']
data = request.get_json() or {}
watch_time = data.get('watch_time', 1)
watch_percentage = data.get('watch_percentage', 0)
completed = data.get('completed', False)
ip = get_client_ip()
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, watch_percentage, completed)
VALUES (?, ?, 1, ?, ?, ?)
ON CONFLICT(user_id, video_id) DO UPDATE SET
watched = 1,
watch_time = watch_time + ?,
watch_percentage = MAX(watch_percentage, ?),
completed = MAX(completed, ?)
''', (user_id, video_id, watch_time, watch_percentage, completed,
watch_time, watch_percentage, completed))
db.execute('UPDATE videos SET views = views + 1 WHERE id = ?', (video_id,))
else:
db.execute('''
UPDATE interactions SET
watch_time = watch_time + ?,
watch_percentage = MAX(watch_percentage, ?),
completed = MAX(completed, ?)
WHERE user_id = ? AND video_id = ?
''', (watch_time, watch_percentage, completed, user_id, video_id))
# تسجيل في جدول المشاهدات المفصل
db.execute('''
INSERT INTO video_views (user_id, video_id, watch_time, watch_percentage, completed, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (user_id, video_id, watch_time, watch_percentage, completed, ip,
request.headers.get('User-Agent', '')))
# تحديث متوسط وقت المشاهدة للفيديو
db.execute('''
UPDATE videos SET
avg_watch_time = (
SELECT AVG(watch_time) FROM video_views WHERE video_id = ?
),
completion_rate = (
SELECT AVG(watch_percentage) FROM video_views WHERE video_id = ?
)
WHERE id = ?
''', (video_id, video_id, video_id))
db.commit()
# XP للمشاهدة
if completed:
add_xp(user_id, 'complete_view', 5)
elif watch_percentage > 50:
add_xp(user_id, 'partial_view', 2)
track_event(user_id, 'view_video', {
'video_id': video_id,
'watch_time': watch_time,
'watch_percentage': watch_percentage,
'completed': completed
})
return jsonify({'status': 'ok'})
except Exception as e:
logger.error(f"خطأ في view_video: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/share/<int:video_id>', methods=['POST'])
@login_required
def share_video(video_id):
try:
user_id = session['user_id']
platform = request.get_json().get('platform', 'internal')
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, 10, 'مكافأة مشاركة فيديو')
add_xp(user_id, 'share', 5)
track_event(user_id, 'share_video', {'video_id': video_id, 'platform': platform})
return jsonify({'status': 'ok', 'shares_count': shares})
except Exception as e:
logger.error(f"خطأ في share_video: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/save/<int:video_id>', 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"خطأ في save_video: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/saved')
@login_required
def get_saved_videos():
"""الحصول على الفيديوهات المحفوظة"""
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 20))
offset = (page - 1) * limit
db = get_db()
videos = db.execute('''
SELECT v.*, u.username, u.avatar, u.is_verified
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
LIMIT ? OFFSET ?
''', (session['user_id'], 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)
result.append(video)
return jsonify({
'videos': result,
'next_page': page + 1 if len(result) == limit else None
})
except Exception as e:
logger.error(f"خطأ في get_saved_videos: {e}")
return jsonify([]), 500
# ==================== نظام التعليقات المتقدم ====================
@app.route('/api/comments/<int:video_id>')
@login_required
def get_comments(video_id):
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 20))
offset = (page - 1) * limit
db = get_db()
# التعليقات الرئيسية
comments = db.execute('''
SELECT c.*, u.username, u.avatar, u.is_verified, u.role,
(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 ? OFFSET ?
''', (video_id, limit, offset)).fetchall()
result = []
for comment in comments:
c = dict(comment)
# الردود على هذا التعليق
replies = db.execute('''
SELECT c.*, u.username, u.avatar, u.is_verified, u.role,
(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 5
''', (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)
total = db.execute('SELECT COUNT(*) FROM comments WHERE video_id = ? AND parent_id IS NULL',
(video_id,)).fetchone()[0]
return jsonify({
'comments': result,
'total': total,
'page': page,
'has_more': len(result) == limit
})
except Exception as e:
logger.error(f"خطأ في get_comments: {e}")
return jsonify({'comments': [], 'error': str(e)}), 500
@app.route('/api/comment/<int:video_id>', methods=['POST'])
@login_required
@limiter.limit("20 per minute")
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) > 1000:
return jsonify({'error': 'التعليق طويل جداً'}), 400
# فحص التعليق تلقائياً
moderation = moderate_comment(comment_text)
if not moderation['is_appropriate']:
if moderation['action'] == 'block':
return jsonify({'error': 'التعليق غير مناسب'}), 400
elif moderation['action'] == 'flag':
# سيتم مراجعة التعليق
pass
db = get_db()
cursor = db.execute('''
INSERT INTO comments (user_id, video_id, parent_id, comment_text, is_moderated)
VALUES (?, ?, ?, ?, ?)
''', (user_id, video_id, parent_id, comment_text,
not moderation['is_appropriate']))
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()
add_notification(video['user_id'], user_id, 'comment',
f'علق {user["username"]} على فيديو لك',
video_id=video_id, comment_id=comment_id,
priority=1)
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()
add_notification(parent['user_id'], user_id, 'reply',
f'رد {user["username"]} على تعليقك',
video_id=video_id, comment_id=comment_id,
priority=1)
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"خطأ في add_comment: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/comment/like/<int:comment_id>', 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()
add_notification(comment['user_id'], user_id, 'like_comment',
f'أعجب {user["username"]} بتعليقك',
priority=0)
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"خطأ في like_comment: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/comment/pin/<int:comment_id>', 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"خطأ في pin_comment: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/comment/delete/<int:comment_id>', 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('role') in ['admin', 'moderator']
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"خطأ في delete_comment: {e}")
return jsonify({'error': str(e)}), 500
# ==================== نظام الإشعارات المباشر ====================
@app.route('/api/notifications/stream')
@login_required
def notification_stream():
"""بث الإشعارات المباشر باستخدام Server-Sent Events"""
user_id = session['user_id']
def event_stream():
q = queue.Queue()
notification_queues[user_id] = q
try:
while True:
try:
notification = q.get(timeout=30)
yield f"event: notification\ndata: {json.dumps(notification, ensure_ascii=False)}\n\n"
except queue.Empty:
yield "event: ping\ndata: {}\n\n"
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',
'Connection': 'keep-alive'
}
)
@app.route('/api/notifications')
@login_required
def get_notifications():
"""الحصول على الإشعارات مع ترقيم الصفحات"""
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 20))
offset = (page - 1) * limit
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.priority DESC, n.created_at DESC
LIMIT ? OFFSET ?
''', (session['user_id'], limit, offset)).fetchall()
# تحديث حالة القراءة
if page == 1:
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)
unread_count = db.execute('''
SELECT COUNT(*) FROM notifications
WHERE user_id = ? AND is_read = 0
''', (session['user_id'],)).fetchone()[0]
return jsonify({
'notifications': result,
'unread_count': unread_count,
'next_page': page + 1 if len(result) == limit else None
})
except Exception as e:
logger.error(f"خطأ في get_notifications: {e}")
return jsonify({'notifications': [], 'unread_count': 0}), 500
@app.route('/api/notifications/count')
@login_required
def notification_count():
"""عدد الإشعارات غير المقروءة"""
try:
db = get_db()
count = db.execute('SELECT COUNT(*) FROM notifications WHERE user_id = ? AND is_read = 0',
(session['user_id'],)).fetchone()[0]
return jsonify({'count': count})
except Exception as e:
logger.error(f"خطأ في notification_count: {e}")
return jsonify({'count': 0}), 500
@app.route('/api/notifications/mark-read', methods=['POST'])
@login_required
def mark_notifications_read():
"""تحديد الإشعارات كمقروءة"""
try:
notification_id = request.get_json().get('notification_id')
db = get_db()
if notification_id:
db.execute('UPDATE notifications SET is_read = 1 WHERE id = ? AND user_id = ?',
(notification_id, session['user_id']))
else:
db.execute('UPDATE notifications SET is_read = 1 WHERE user_id = ?',
(session['user_id'],))
db.commit()
return jsonify({'status': 'ok'})
except Exception as e:
logger.error(f"خطأ في mark_notifications_read: {e}")
return jsonify({'error': str(e)}), 500
# ==================== نظام البث المباشر المتقدم ====================
@app.route('/live')
@login_required
def live_page():
"""صفحة البث المباشر"""
return render_template_string(LIVE_TEMPLATE, session=session)
@app.route('/api/live/start', methods=['POST'])
@login_required
def start_live():
"""بدء بث مباشر (يتطلب عضوية VIP)"""
try:
# التحقق من صلاحية البث
benefits = get_membership_benefits(session['user_id'])
if not benefits.can_live_stream:
return jsonify({'error': 'البث المباشر متاح فقط لأعضاء VIP'}), 403
data = request.get_json()
title = data.get('title', 'بث مباشر جديد')
description = data.get('description', '')
db = get_db()
stream_key = hashlib.sha256(f"{session['user_id']}_{uuid.uuid4()}_{time.time()}".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'],))
cursor = db.execute('''
INSERT INTO live_streams (user_id, stream_key, title, description)
VALUES (?, ?, ?, ?)
''', (session['user_id'], stream_key, title, description))
stream_id = cursor.lastrowid
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()
}
# إشعار للمتابعين
followers = db.execute('SELECT follower_id FROM follows WHERE user_id = ?',
(session['user_id'],)).fetchall()
follower_ids = [f['follower_id'] for f in followers]
send_bulk_notifications(
follower_ids,
session['user_id'],
'live_start',
f'{session["username"]} بدأ بثاً مباشراً!',
action_url=f'/live/{stream_id}',
priority=2
)
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"خطأ في 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"خطأ في 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, u.is_verified
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"خطأ في get_active_streams: {e}")
return jsonify([]), 500
@app.route('/api/live/<int:stream_id>/view', methods=['POST'])
@login_required
def view_stream(stream_id):
"""تسجيل مشاهدة بث"""
try:
db = get_db()
db.execute('UPDATE live_streams SET viewers = viewers + 1, peak_viewers = MAX(peak_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"خطأ في view_stream: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/live/gift/<int:stream_id>', methods=['POST'])
@login_required
def send_gift(stream_id):
"""إرسال هدية في البث"""
try:
data = request.get_json()
gift_id = data.get('gift_id')
db = get_db()
# الحصول على معلومات الهدية
gift = db.execute('SELECT * FROM virtual_gifts WHERE id = ?', (gift_id,)).fetchone()
if not gift:
return jsonify({'error': 'الهدية غير موجودة'}), 404
price = gift['price']
# التحقق من الرصيد
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["name"]}'):
# تسجيل الهدية
cursor = db.execute('''
INSERT INTO live_gifts (stream_id, user_id, gift_type, gift_value)
VALUES (?, ?, ?, ?)
''', (stream_id, session['user_id'], gift['name'], price))
gift_record_id = cursor.lastrowid
# إضافة قيمة الهدية لصاحب البث
stream = db.execute('SELECT user_id FROM live_streams WHERE id = ?', (stream_id,)).fetchone()
if stream:
stream_owner = stream['user_id']
owner_share = price // 2 # نصف قيمة الهدية لصاحب البث
add_coins(stream_owner, owner_share, f'هدية {gift["name"]} من {session["username"]}')
# تحديث إجمالي الهدايا في البث
db.execute('UPDATE live_streams SET gifts_value = gifts_value + ? WHERE id = ?',
(price, stream_id))
# إرسال إشعار لصاحب البث
add_notification(stream_owner, session['user_id'], 'gift',
f'أرسل {session["username"]} هدية {gift["name"]} في بثك',
gift_id=gift_record_id,
priority=2,
image_url=gift['image_url'])
db.commit()
track_event(session['user_id'], 'send_gift',
{'stream_id': stream_id, 'gift_id': gift_id, 'price': price})
return jsonify({'status': 'ok'})
else:
return jsonify({'error': 'فشل في خصم العملات'}), 400
except Exception as e:
logger.error(f"خطأ في send_gift: {e}")
return jsonify({'error': str(e)}), 500
# ==================== نظام النقاط والمكافآت ====================
@app.route('/api/gamification/xp')
@login_required
def get_xp():
"""الحصول على نقاط الخبرة والتاريخ"""
db = get_db()
user = db.execute('SELECT xp, level FROM users WHERE id = ?', (session['user_id'],)).fetchone()
# تاريخ النقاط
history = db.execute('''
SELECT action, xp_gained, created_at FROM user_xp
WHERE user_id = ? ORDER BY created_at DESC LIMIT 50
''', (session['user_id'],)).fetchall()
# النقاط المطلوبة للمستوى التالي
current_level_xp = calculate_xp_for_level(user['level'])
next_level_xp = calculate_xp_for_level(user['level'] + 1)
progress = ((user['xp'] - current_level_xp) / (next_level_xp - current_level_xp)) * 100 if next_level_xp > current_level_xp else 100
return jsonify({
'xp': user['xp'],
'level': user['level'],
'next_level_xp': next_level_xp,
'progress': min(progress, 100),
'history': [dict(h) for h in history]
})
@app.route('/api/gamification/daily', methods=['POST'])
@login_required
def claim_daily():
"""المطالبة بالمكافأة اليومية"""
success, *rest = claim_daily_reward(session['user_id'])
if success:
coins, xp, streak = rest
track_event(session['user_id'], 'claim_daily', {'coins': coins, 'xp': xp, 'streak': streak})
return jsonify({'status': 'ok', 'coins': coins, 'xp': xp, 'streak': streak})
else:
return jsonify({'error': rest[0]}), 400
@app.route('/api/gamification/leaderboard')
@login_required
def leaderboard():
"""لوحة المتصدرين"""
period = request.args.get('period', 'all') # all, weekly, daily
limit = int(request.args.get('limit', 50))
db = get_db()
if period == 'daily':
# المتصدرين اليومي (بناءً على XP اليوم)
users = db.execute('''
SELECT u.username, u.avatar, u.is_verified, SUM(x.xp_gained) as daily_xp, u.level
FROM user_xp x
JOIN users u ON x.user_id = u.id
WHERE DATE(x.created_at) = DATE('now')
GROUP BY x.user_id
ORDER BY daily_xp DESC
LIMIT ?
''', (limit,)).fetchall()
elif period == 'weekly':
# المتصدرين الأسبوعي
users = db.execute('''
SELECT u.username, u.avatar, u.is_verified, SUM(x.xp_gained) as weekly_xp, u.level
FROM user_xp x
JOIN users u ON x.user_id = u.id
WHERE x.created_at > datetime('now', '-7 days')
GROUP BY x.user_id
ORDER BY weekly_xp DESC
LIMIT ?
''', (limit,)).fetchall()
else:
# المتصدرين الشامل
users = db.execute('''
SELECT username, avatar, xp, level, is_verified
FROM users
ORDER BY xp DESC
LIMIT ?
''', (limit,)).fetchall()
result = []
for u in users:
user = dict(u)
user['avatar_url'] = f'/avatars/{u["avatar"]}'
result.append(user)
# موقع المستخدم الحالي
if period == 'all':
rank = db.execute('''
SELECT COUNT(*) + 1 as rank
FROM users
WHERE xp > (SELECT xp FROM users WHERE id = ?)
''', (session['user_id'],)).fetchone()[0]
else:
rank = None
return jsonify({
'leaderboard': result,
'user_rank': rank
})
@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"خطأ في coins_balance: {e}")
return jsonify({'coins': 0, 'diamonds': 0}), 500
@app.route('/api/transactions')
@login_required
def get_transactions():
"""سجل المعاملات"""
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 20))
offset = (page - 1) * limit
db = get_db()
transactions = db.execute('''
SELECT * FROM transactions
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
''', (session['user_id'], limit, offset)).fetchall()
return jsonify({
'transactions': [dict(t) for t in transactions],
'next_page': page + 1 if len(transactions) == limit else None
})
except Exception as e:
logger.error(f"خطأ في get_transactions: {e}")
return jsonify({'transactions': []}), 500
@app.route('/api/gifts')
@login_required
def get_gifts():
"""الحصول على قائمة الهدايا المتاحة"""
try:
db = get_db()
gifts = db.execute('SELECT * FROM virtual_gifts WHERE is_active = 1 ORDER BY price').fetchall()
return jsonify([dict(g) for g in gifts])
except Exception as e:
logger.error(f"خطأ في get_gifts: {e}")
return jsonify([]), 500
# ==================== نظام التحليلات ====================
def track_event(user_id, event_name, event_data=None):
"""تسجيل حدث تحليلي"""
db = get_db()
try:
db.execute('''
INSERT INTO analytics_events (user_id, session_id, event_name, event_data, ip_address, user_agent, page_url)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (user_id, session.get('session_id'), event_name,
json.dumps(event_data) if event_data else None,
get_client_ip(),
request.headers.get('User-Agent', ''),
request.path))
db.commit()
except Exception as e:
logger.error(f"خطأ في track_event: {e}")
@app.route('/api/analytics/stats')
@login_required
def analytics_stats():
"""إحصائيات المستخدم"""
stats = get_user_stats(session['user_id'])
return jsonify(stats)
@app.route('/api/analytics/track', methods=['POST'])
@login_required
def track_analytics():
"""تتبع حدث مخصص"""
data = request.get_json()
event_name = data.get('event_name')
event_data = data.get('event_data')
if not event_name:
return jsonify({'error': 'event_name مطلوب'}), 400
track_event(session['user_id'], event_name, event_data)
return jsonify({'status': 'ok'})
# ==================== نظام الإحالات والتسويق ====================
def generate_affiliate_link(user_id):
"""توليد رابط إحالة فريد"""
code = secrets.token_urlsafe(10)
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_logs', f"clicks_{datetime.now().strftime('%Y%m')}.log")
with open(log_file, 'a', encoding='utf-8') as f:
f.write(f"{datetime.now().isoformat()}|{referrer['id']}|{ip}|{user_agent}\n")
return True
except Exception as e:
logger.error(f"خطأ في track_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 = 200
coins_referred = 100
xp_referrer = 50
add_coins(referrer['id'], coins_referrer, 'مكافأة إحالة')
add_coins(new_user_id, coins_referred, 'مكافأة تسجيل عن طريق رابط')
add_xp(referrer['id'], 'referral', xp_referrer)
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_logs', f"conversions_{datetime.now().strftime('%Y%m')}.log")
with open(log_file, 'a', encoding='utf-8') as f:
f.write(f"{datetime.now().isoformat()}|{referrer['id']}|{new_user_id}\n")
except Exception as e:
logger.error(f"خطأ في process_affiliate_conversion: {e}")
@app.route('/api/affiliate/link')
@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')
@login_required
def affiliate_stats():
"""إحصائيات الإحالات"""
db = get_db()
user = db.execute('''
SELECT affiliate_clicks, affiliate_conversions, affiliate_balance
FROM users WHERE id = ?
''', (session['user_id'],)).fetchone()
clicks = db.execute('''
SELECT clicked_at, converted FROM affiliate_clicks
WHERE referrer_id = ? ORDER BY clicked_at DESC LIMIT 50
''', (session['user_id'],)).fetchall()
return jsonify({
'clicks': user['affiliate_clicks'],
'conversions': user['affiliate_conversions'],
'balance': user['affiliate_balance'],
'recent_clicks': [dict(c) for c in clicks]
})
@app.route('/r/<referral_code>')
def referral_redirect(referral_code):
"""تسجيل نقرة وإعادة توجيه للتسجيل"""
ip = get_client_ip()
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/report', methods=['POST'])
@login_required
def create_report():
"""إنشاء بلاغ"""
try:
data = request.get_json()
report_type = data.get('type') # user, video, comment
reported_id = data.get('id')
reason = data.get('reason')
details = data.get('details', '')
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, details)
VALUES (?, ?, ?, ?)
''', (session['user_id'], reported_id, reason, details))
elif report_type == 'video':
db.execute('''
INSERT INTO reports (reporter_id, reported_video_id, reason, details)
VALUES (?, ?, ?, ?)
''', (session['user_id'], reported_id, reason, details))
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, details)
VALUES (?, ?, ?, ?)
''', (session['user_id'], reported_id, reason, details))
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"خطأ في create_report: {e}")
return jsonify({'error': str(e)}), 500
# ==================== دوال الملفات ====================
@app.route('/videos/<path:filename>')
def serve_video(filename):
"""تقديم ملفات الفيديو"""
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
@app.route('/avatars/<path:filename>')
def serve_avatar(filename):
"""تقديم الصور الشخصية"""
return send_from_directory(app.config['AVATAR_FOLDER'], filename)
@app.route('/thumbnails/<path:filename>')
def serve_thumbnail(filename):
"""تقديم الصور المصغرة"""
return send_from_directory(app.config['THUMBNAIL_FOLDER'], filename)
@app.route('/encrypted/<path:filename>')
@login_required
def serve_encrypted(filename):
"""تقديم الفيديوهات المشفرة"""
return send_from_directory(app.config['ENCRYPTED_FOLDER'], filename)
@app.route('/watermarked/<path:filename>')
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)
@app.route('/leaderboard')
@login_required
def leaderboard_page():
"""صفحة المتصدرين"""
return render_template_string(LEADERBOARD_TEMPLATE, session=session)
@app.route('/gifts')
@login_required
def gifts_page():
"""صفحة الهدايا"""
return render_template_string(GIFTS_TEMPLATE, session=session)
# ==================== قوالب HTML المتقدمة ====================
LOGIN_PAGE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تسجيل الدخول - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
.gradient-text {
background: linear-gradient(135deg, #ff2d55, #ff8a5c);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.btn-primary {
background: linear-gradient(135deg, #ff2d55, #ff4d6d);
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(255, 45, 85, 0.5);
}
.input-glass {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.input-glass:focus {
border-color: #ff2d55;
box-shadow: 0 0 0 2px rgba(255, 45, 85, 0.2);
}
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] flex items-center justify-center p-4">
<div class="absolute inset-0 overflow-hidden">
<div class="absolute top-0 -left-4 w-72 h-72 bg-purple-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-blob"></div>
<div class="absolute top-0 -right-4 w-72 h-72 bg-yellow-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-blob animation-delay-2000"></div>
<div class="absolute -bottom-8 left-20 w-72 h-72 bg-pink-300 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-blob animation-delay-4000"></div>
</div>
<div class="glass-card rounded-3xl p-8 w-full max-w-md relative z-10">
<div class="text-center mb-8">
<h1 class="text-6xl font-black gradient-text">ARC</h1>
<p class="text-gray-400 mt-2">منصة الفيديو الأكثر تطوراً</p>
</div>
<form method="post" class="space-y-6">
<div>
<label class="block text-gray-300 mb-2 text-sm">اسم المستخدم أو البريد</label>
<input type="text" name="username" required
class="w-full px-4 py-3 input-glass rounded-2xl text-white focus:outline-none">
</div>
<div>
<label class="block text-gray-300 mb-2 text-sm">كلمة المرور</label>
<input type="password" name="password" required
class="w-full px-4 py-3 input-glass rounded-2xl text-white focus:outline-none">
</div>
<div class="flex items-center justify-between">
<label class="flex items-center text-gray-300">
<input type="checkbox" name="remember" class="form-checkbox text-[#ff2d55] rounded bg-transparent border-gray-600">
<span class="mr-2 text-sm">تذكرني</span>
</label>
<a href="/forgot-password" class="text-sm text-[#ff2d55] hover:text-[#ff4d6d] transition">نسيت كلمة المرور؟</a>
</div>
<button type="submit" class="w-full btn-primary text-white font-bold py-3 px-4 rounded-2xl text-lg">
دخول
</button>
</form>
{% if error %}
<div class="mt-4 p-3 bg-red-500/20 border border-red-500/50 rounded-2xl text-red-400 text-center">
{{ error }}
</div>
{% endif %}
<div class="mt-6 text-center">
<p class="text-gray-400">ليس لديك حساب؟
<a href="/register" class="text-[#ff2d55] hover:text-[#ff4d6d] font-bold transition">سجل الآن</a>
</p>
</div>
<div class="mt-8 pt-6 border-t border-gray-800 text-center">
<p class="text-xs text-gray-500">
بالتسجيل أنت توافق على
<a href="#" class="text-gray-400 hover:text-[#ff2d55]">الشروط والأحكام</a>
و
<a href="#" class="text-gray-400 hover:text-[#ff2d55]">سياسة الخصوصية</a>
</p>
</div>
</div>
</body>
</html>
'''
REGISTER_PAGE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تسجيل جديد - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
.gradient-text {
background: linear-gradient(135deg, #ff2d55, #ff8a5c);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.btn-primary {
background: linear-gradient(135deg, #ff2d55, #ff4d6d);
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(255, 45, 85, 0.5);
}
.input-glass {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.input-glass:focus {
border-color: #ff2d55;
box-shadow: 0 0 0 2px rgba(255, 45, 85, 0.2);
}
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] flex items-center justify-center p-4">
<div class="glass-card rounded-3xl p-8 w-full max-w-md">
<div class="text-center mb-8">
<h1 class="text-5xl font-black gradient-text">انضم إلينا</h1>
<p class="text-gray-400 mt-2">أنشئ حسابك وابدأ الإبداع</p>
</div>
<form method="post" class="space-y-4">
<div>
<label class="block text-gray-300 mb-2 text-sm">اسم المستخدم</label>
<input type="text" name="username" required
class="w-full px-4 py-3 input-glass rounded-2xl text-white focus:outline-none">
</div>
<div>
<label class="block text-gray-300 mb-2 text-sm">البريد الإلكتروني</label>
<input type="email" name="email" required
class="w-full px-4 py-3 input-glass rounded-2xl text-white focus:outline-none">
</div>
<div>
<label class="block text-gray-300 mb-2 text-sm">كلمة المرور</label>
<input type="password" name="password" required
class="w-full px-4 py-3 input-glass rounded-2xl text-white focus:outline-none">
<p class="text-xs text-gray-500 mt-1">8 أحرف على الأقل، حرف كبير وصغير ورقم</p>
</div>
<div>
<label class="block text-gray-300 mb-2 text-sm">تأكيد كلمة المرور</label>
<input type="password" name="confirm_password" required
class="w-full px-4 py-3 input-glass rounded-2xl text-white focus:outline-none">
</div>
<div>
<label class="block text-gray-300 mb-2 text-sm">كود الإحالة (اختياري)</label>
<input type="text" name="referral_code" value="{{ request.args.get('ref', '') }}"
class="w-full px-4 py-3 input-glass rounded-2xl text-white focus:outline-none">
</div>
<label class="flex items-center text-gray-300">
<input type="checkbox" name="agree_terms" required class="form-checkbox text-[#ff2d55] rounded bg-transparent border-gray-600">
<span class="mr-2 text-sm">أوافق على <a href="#" class="text-[#ff2d55]">الشروط والأحكام</a></span>
</label>
<button type="submit" class="w-full btn-primary text-white font-bold py-3 px-4 rounded-2xl text-lg mt-4">
تسجيل
</button>
</form>
{% if error %}
<div class="mt-4 p-3 bg-red-500/20 border border-red-500/50 rounded-2xl text-red-400 text-center">
{{ error }}
</div>
{% endif %}
<div class="mt-6 text-center">
<p class="text-gray-400">لديك حساب بالفعل؟
<a href="/login" class="text-[#ff2d55] hover:text-[#ff4d6d] font-bold transition">سجل دخول</a>
</p>
</div>
</div>
</body>
</html>
'''
TWOFA_PAGE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>المصادقة الثنائية - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
.btn-primary {
background: linear-gradient(135deg, #ff2d55, #ff4d6d);
}
.input-glass {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
}
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] flex items-center justify-center p-4">
<div class="glass-card rounded-3xl p-8 w-full max-w-md">
<div class="text-center mb-8">
<div class="text-5xl mb-4">🔐</div>
<h2 class="text-2xl font-bold text-white">المصادقة الثنائية</h2>
<p class="text-gray-400 mt-2">أدخل الرمز من تطبيق المصادقة</p>
</div>
<form method="post" action="/login" class="space-y-6">
<input type="hidden" name="username" value="{{ request.form.username }}">
<input type="hidden" name="password" value="{{ request.form.password }}">
<div>
<input type="text" name="twofa_code" required
class="w-full px-4 py-4 input-glass rounded-2xl text-white text-center text-2xl tracking-widest focus:outline-none focus:border-[#ff2d55]"
placeholder="000000" maxlength="6">
</div>
<button type="submit" class="w-full btn-primary text-white font-bold py-3 px-4 rounded-2xl">
تحقق
</button>
</form>
<div class="mt-6 text-center">
<a href="/login" class="text-gray-400 hover:text-[#ff2d55] transition">العودة لتسجيل الدخول</a>
</div>
</div>
</body>
</html>
'''
SETUP_2FA_PAGE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>إعداد المصادقة الثنائية - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
.btn-primary {
background: linear-gradient(135deg, #ff2d55, #ff4d6d);
}
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] p-4">
<div class="max-w-2xl mx-auto">
<div class="glass-card rounded-3xl p-8">
<h1 class="text-3xl font-bold text-white mb-6 text-center">إعداد المصادقة الثنائية</h1>
<div class="space-y-8">
<div class="text-center">
<div class="text-6xl mb-4">📱</div>
<h2 class="text-xl font-bold text-white mb-2">1. ثبّت تطبيق مصادقة</h2>
<p class="text-gray-400">حمّل Google Authenticator أو Microsoft Authenticator</p>
</div>
<div class="text-center">
<h2 class="text-xl font-bold text-white mb-4">2. امسح رمز QR</h2>
{% if qr_code %}
<img src="data:image/png;base64,{{ qr_code }}" class="mx-auto bg-white p-4 rounded-2xl">
{% endif %}
</div>
<div class="text-center">
<h2 class="text-xl font-bold text-white mb-2">أو أدخل المفتاح يدوياً</h2>
<div class="bg-gray-800 p-3 rounded-xl">
<code class="text-[#ff2d55]">{{ secret }}</code>
</div>
</div>
<div>
<h2 class="text-xl font-bold text-white mb-4">3. رموز الاسترداد الاحتياطية</h2>
<div class="bg-gray-800 p-4 rounded-xl grid grid-cols-2 gap-2">
{% for code in backup_codes %}
<div class="text-center font-mono text-[#ff2d55]">{{ code }}</div>
{% endfor %}
</div>
<p class="text-sm text-gray-400 mt-2">احفظ هذه الرموز في مكان آمن، يمكنك استخدامها مرة واحدة إذا فقدت هاتفك</p>
</div>
<div class="text-center">
<h2 class="text-xl font-bold text-white mb-4">4. تحقق من الإعداد</h2>
<form method="post" class="max-w-xs mx-auto space-y-4">
<input type="text" name="code" placeholder="أدخل الرمز"
class="w-full px-4 py-3 bg-gray-800 border border-gray-700 rounded-2xl text-white text-center"
maxlength="6">
<button type="submit" class="w-full btn-primary text-white font-bold py-3 px-4 rounded-2xl">
تحقق وفعّل
</button>
</form>
{% if error %}
<p class="text-red-400 mt-2">{{ error }}</p>
{% endif %}
</div>
</div>
</div>
</div>
</body>
</html>
'''
MAIN_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>ARC Video - الصفحة الرئيسية</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-sidebar {
background: rgba(20, 20, 30, 0.8);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-left: 1px solid rgba(255, 45, 85, 0.2);
}
.glass-nav {
background: rgba(10, 10, 15, 0.9);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-top: 1px solid rgba(255, 45, 85, 0.2);
}
.fab {
background: linear-gradient(135deg, #ff2d55, #ff4d6d);
box-shadow: 0 10px 25px -5px rgba(255, 45, 85, 0.5);
transition: all 0.3s ease;
}
.fab:hover {
transform: scale(1.1) translateY(-5px);
}
.video-container {
scroll-snap-type: y mandatory;
height: calc(100vh - 80px);
overflow-y: scroll;
scroll-behavior: smooth;
}
.video-item {
scroll-snap-align: start;
height: 100vh;
position: relative;
}
.gradient-text {
background: linear-gradient(135deg, #ff2d55, #ff8a5c);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.action-btn {
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.action-btn:hover, .action-btn.active {
background: #ff2d55;
transform: scale(1.1);
}
.comments-panel {
background: rgba(20, 20, 30, 0.95);
backdrop-filter: blur(20px);
border-top: 1px solid rgba(255, 45, 85, 0.3);
}
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.slide-up {
animation: slideUp 0.3s ease-out;
}
.notification-badge {
position: absolute;
top: -5px;
right: -5px;
background: #ff2d55;
color: white;
border-radius: 999px;
min-width: 18px;
height: 18px;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #1a1a1a;
}
.vip-badge {
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #000;
font-weight: bold;
}
.gold-badge {
background: linear-gradient(135deg, #FFD700, #B8860B);
color: #000;
}
</style>
</head>
<body class="bg-black text-white h-screen overflow-hidden">
<!-- الشريط الجانبي الأيمن (Glassmorphism) -->
<div class="fixed right-0 top-0 h-full w-64 glass-sidebar hidden lg:block p-6 z-30">
<div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-2xl bg-gradient-to-br from-[#ff2d55] to-[#ff8a5c] flex items-center justify-center">
<span class="text-2xl font-black">A</span>
</div>
<span class="text-2xl font-black gradient-text">ARC</span>
</div>
<nav class="space-y-2">
<a href="#" onclick="loadFeed('for-you'); return false;" class="flex items-center gap-3 px-4 py-3 rounded-2xl bg-[#ff2d55] text-white nav-item active">
<span class="text-xl">🏠</span>
<span>الرئيسية</span>
</a>
<a href="#" onclick="loadFeed('friends'); return false;" class="flex items-center gap-3 px-4 py-3 rounded-2xl hover:bg-white/5 transition nav-item">
<span class="text-xl">👥</span>
<span>الأصدقاء</span>
</a>
<a href="#" onclick="loadFeed('trending'); return false;" class="flex items-center gap-3 px-4 py-3 rounded-2xl hover:bg-white/5 transition nav-item">
<span class="text-xl">🔥</span>
<span>رائج</span>
<span class="mr-auto text-xs bg-[#ff2d55] px-2 py-1 rounded-full">جديد</span>
</a>
<a href="#" onclick="openGlass('/glass/search'); return false;" class="flex items-center gap-3 px-4 py-3 rounded-2xl hover:bg-white/5 transition">
<span class="text-xl">🔍</span>
<span>بحث</span>
</a>
<a href="#" onclick="openGlass('/glass/saved'); return false;" class="flex items-center gap-3 px-4 py-3 rounded-2xl hover:bg-white/5 transition">
<span class="text-xl">📁</span>
<span>محفوظة</span>
</a>
<a href="#" onclick="openNotifications(); return false;" class="flex items-center gap-3 px-4 py-3 rounded-2xl hover:bg-white/5 transition relative">
<span class="text-xl">🔔</span>
<span>إشعارات</span>
<span id="sidebarNotifBadge" class="notification-badge hidden">0</span>
</a>
<a href="#" onclick="openGlass('/glass/leaderboard'); return false;" class="flex items-center gap-3 px-4 py-3 rounded-2xl hover:bg-white/5 transition">
<span class="text-xl">🏆</span>
<span>المتصدرين</span>
</a>
</nav>
<div class="absolute bottom-6 right-6 left-6">
<div class="bg-white/5 rounded-2xl p-4">
<div class="flex items-center gap-3">
<div class="relative">
<img src="/avatars/{{ session.avatar or 'default.jpg' }}" class="w-12 h-12 rounded-2xl object-cover border-2 border-[#ff2d55]">
<div class="absolute -bottom-1 -left-1 w-4 h-4 bg-green-500 rounded-full border-2 border-black"></div>
</div>
<div class="flex-1">
<div class="font-bold">@{{ session.username }}</div>
<div class="text-xs text-gray-400 flex items-center gap-1">
<span>المستوى {{ session.level or 1 }}</span>
<span class="w-1 h-1 bg-gray-600 rounded-full"></span>
<span>XP: {{ session.xp or 0 }}</span>
</div>
</div>
<a href="/settings" class="text-2xl text-gray-400 hover:text-[#ff2d55] transition">⚙️</a>
</div>
<div class="mt-3 flex justify-between text-xs">
<a href="/profile/{{ session.user_id }}" class="text-center">
<div class="font-bold">{{ session.followers_count or 0 }}</div>
<div class="text-gray-500">متابع</div>
</a>
<a href="/profile/{{ session.user_id }}?tab=following" class="text-center">
<div class="font-bold">{{ session.following_count or 0 }}</div>
<div class="text-gray-500">يتابع</div>
</a>
<a href="/profile/{{ session.user_id }}" class="text-center">
<div class="font-bold">{{ session.videos_count or 0 }}</div>
<div class="text-gray-500">فيديو</div>
</a>
</div>
</div>
</div>
</div>
<!-- المحتوى الرئيسي -->
<div class="lg:mr-64 h-screen">
<div id="videoContainer" class="video-container"></div>
</div>
<!-- Floating Action Button (تصوير) -->
<a href="/upload" class="fab fixed left-6 bottom-24 lg:bottom-6 w-14 h-14 rounded-full flex items-center justify-center text-2xl z-40">
📸
</a>
<!-- الشريط السفلي للموبايل -->
<div class="lg:hidden glass-nav fixed bottom-0 left-0 right-0 h-20 flex items-center justify-around px-4 z-30">
<a href="#" onclick="loadFeed('for-you'); return false;" class="flex flex-col items-center text-gray-400 hover:text-[#ff2d55] transition nav-item-mobile active">
<span class="text-2xl">🏠</span>
<span class="text-xs">الرئيسية</span>
</a>
<a href="#" onclick="loadFeed('friends'); return false;" class="flex flex-col items-center text-gray-400 hover:text-[#ff2d55] transition nav-item-mobile">
<span class="text-2xl">👥</span>
<span class="text-xs">الأصدقاء</span>
</a>
<a href="#" onclick="loadFeed('trending'); return false;" class="flex flex-col items-center text-gray-400 hover:text-[#ff2d55] transition nav-item-mobile">
<span class="text-2xl">🔥</span>
<span class="text-xs">رائج</span>
</a>
<a href="#" onclick="openGlass('/glass/search'); return false;" class="flex flex-col items-center text-gray-400 hover:text-[#ff2d55] transition">
<span class="text-2xl">🔍</span>
<span class="text-xs">بحث</span>
</a>
<a href="#" onclick="openGlass('/glass/profile/{{ session.user_id }}'); return false;" class="flex flex-col items-center text-gray-400 hover:text-[#ff2d55] transition">
<span class="text-2xl">👤</span>
<span class="text-xs">حسابي</span>
</a>
</div>
<!-- لوحة التعليقات -->
<div id="commentsPanel" class="comments-panel fixed bottom-0 left-0 right-0 lg:left-auto lg:right-64 lg:w-96 rounded-t-3xl lg:rounded-tr-none lg:rounded-l-3xl transform translate-y-full transition-transform duration-300 z-50 max-h-[80vh] flex flex-col">
<div class="p-4 border-b border-gray-800 flex justify-between items-center">
<h3 class="font-bold text-lg">💬 التعليقات</h3>
<button onclick="closeComments()" class="text-gray-400 hover:text-white text-2xl">✕</button>
</div>
<div id="commentsList" class="flex-1 overflow-y-auto p-4 space-y-4"></div>
<div class="p-4 border-t border-gray-800">
<div class="flex gap-2">
<input type="text" id="commentInput" placeholder="أضف تعليقاً..."
class="flex-1 bg-white/5 rounded-2xl px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-[#ff2d55]">
<button onclick="sendComment()" class="bg-[#ff2d55] px-6 py-2 rounded-2xl font-bold">إرسال</button>
</div>
</div>
</div>
<!-- طبقة الزجاج (Glass Layer) للملفات الشخصية -->
<div id="glassLayer" class="fixed inset-0 bg-black/90 backdrop-blur-xl z-[100] hidden overflow-y-auto">
<div class="min-h-screen flex items-center justify-center p-4">
<div class="relative w-full max-w-4xl">
<button onclick="closeGlass()" class="absolute -top-2 -left-2 w-12 h-12 rounded-full bg-[#ff2d55] flex items-center justify-center text-2xl z-10">
</button>
<div id="glassContent" class="glass-sidebar rounded-3xl p-6"></div>
</div>
</div>
</div>
<script>
let currentPage = 1;
let loading = false;
let hasMore = true;
let currentFeed = 'for-you';
let commentsVideoId = null;
let replyingTo = null;
let eventSource = null;
const videoContainer = document.getElementById('videoContainer');
const commentsPanel = document.getElementById('commentsPanel');
const commentsList = document.getElementById('commentsList');
const commentInput = document.getElementById('commentInput');
const glassLayer = document.getElementById('glassLayer');
const glassContent = document.getElementById('glassContent');
// تحميل الفيديوهات عند بدء الصفحة
loadVideos();
// التمرير اللانهائي
videoContainer.addEventListener('scroll', () => {
if (videoContainer.scrollTop + videoContainer.clientHeight >= videoContainer.scrollHeight - 100) {
if (!loading && hasMore) {
loadVideos();
}
}
});
// النقر المزدوج لإعجاب سريع
videoContainer.addEventListener('dblclick', (e) => {
const videoItem = e.target.closest('.video-item');
if (!videoItem) return;
const videoId = videoItem.dataset.videoId;
if (videoId) {
likeVideo(videoId, null);
// تأثير قلب
const heart = document.createElement('div');
heart.className = 'fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-7xl z-50 animate-bounce';
heart.innerHTML = '❤️';
heart.style.animation = 'bounce 1s ease';
document.body.appendChild(heart);
setTimeout(() => heart.remove(), 1000);
}
});
function loadVideos() {
if (loading) return;
loading = true;
fetch(`/api/${currentFeed}?page=${currentPage}`)
.then(res => res.json())
.then(data => {
if (!data.videos || data.videos.length === 0) {
hasMore = false;
return;
}
renderVideos(data.videos);
currentPage = data.next_page || currentPage + 1;
hasMore = data.has_more !== false;
})
.finally(() => { loading = false; });
}
function renderVideos(videos) {
videos.forEach(video => {
const container = document.createElement('div');
container.className = 'video-item';
container.dataset.videoId = video.id;
const videoElem = document.createElement('video');
videoElem.src = video.url;
videoElem.loop = true;
videoElem.muted = true;
videoElem.playsInline = true;
videoElem.className = 'w-full h-full object-cover';
// تشغيل/إيقاف عند المشاهدة
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
videoElem.play().catch(() => {});
videoElem.muted = false;
// تسجيل مشاهدة
fetch(`/api/view/${video.id}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({watch_time: 1})
});
// تحديث شريط التقدم
updateProgressBar(video.id);
} else {
videoElem.pause();
videoElem.muted = true;
}
});
}, { threshold: 0.7 });
observer.observe(videoElem);
// معلومات الفيديو
const info = document.createElement('div');
info.className = 'absolute bottom-0 right-0 left-0 p-6 bg-gradient-to-t from-black/90 via-black/50 to-transparent';
info.innerHTML = `
<div class="flex items-center gap-3 mb-2">
<img src="${video.avatar_url}" class="w-12 h-12 rounded-2xl border-2 border-[#ff2d55] cursor-pointer" onclick="event.stopPropagation(); openGlass('/glass/profile/${video.user_id}')">
<div>
<div class="flex items-center gap-2">
<span class="font-bold">@${video.username}</span>
${video.is_verified ? '<span class="text-[#ff2d55]">✓</span>' : ''}
${video.role === 'vip' ? '<span class="vip-badge text-xs px-2 py-0.5 rounded-full">VIP</span>' : ''}
</div>
<div class="text-sm text-gray-300">${video.title || ''}</div>
</div>
</div>
<div class="flex items-center gap-2 text-sm">
<span>❤️ ${video.likes_count || 0}</span>
<span>👁️ ${video.views || 0}</span>
<span>💬 ${video.comments_count || 0}</span>
</div>
`;
// أزرار التفاعل
const actions = document.createElement('div');
actions.className = 'absolute left-4 bottom-24 flex flex-col gap-4';
actions.innerHTML = `
<button onclick="likeVideo(${video.id}, this)" class="action-btn w-12 h-12 rounded-full flex items-center justify-center text-2xl ${video.liked_by_user ? 'active' : ''}">
❤️
</button>
<button onclick="openComments(${video.id})" class="action-btn w-12 h-12 rounded-full flex items-center justify-center text-2xl">
💬
</button>
<button onclick="saveVideo(${video.id}, this)" class="action-btn w-12 h-12 rounded-full flex items-center justify-center text-2xl ${video.saved_by_user ? 'active' : ''}">
📁
</button>
<button onclick="shareVideo(${video.id})" class="action-btn w-12 h-12 rounded-full flex items-center justify-center text-2xl">
↗️
</button>
`;
// شريط التقدم
const progressBar = document.createElement('div');
progressBar.className = 'absolute top-0 right-0 left-0 h-1 bg-gray-800';
progressBar.innerHTML = '<div id="progress-${video.id}" class="h-full bg-[#ff2d55] transition-all duration-300" style="width: 0%"></div>';
container.appendChild(videoElem);
container.appendChild(progressBar);
container.appendChild(info);
container.appendChild(actions);
videoContainer.appendChild(container);
});
}
function updateProgressBar(videoId) {
let progress = 0;
const interval = setInterval(() => {
progress += 1;
const bar = document.getElementById(`progress-${videoId}`);
if (bar) {
bar.style.width = `${progress}%`;
}
if (progress >= 100) {
clearInterval(interval);
fetch(`/api/view/${videoId}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({completed: true, watch_percentage: 100})
});
}
}, 1000);
}
window.likeVideo = function(videoId, btn) {
const liked = btn ? btn.classList.contains('active') : false;
fetch((liked ? '/api/unlike/' : '/api/like/') + videoId, { method: 'POST' })
.then(res => res.json())
.then(data => {
if (data.status === 'ok' && btn) {
btn.classList.toggle('active');
}
});
};
window.saveVideo = function(videoId, btn) {
fetch(`/api/save/${videoId}`, { method: 'POST' })
.then(res => res.json())
.then(data => {
if (data.status === 'ok' && btn) {
btn.classList.toggle('active');
}
});
};
window.shareVideo = function(videoId) {
if (navigator.share) {
navigator.share({
title: 'ARC Video',
text: 'شاهد هذا الفيديو الرائع!',
url: window.location.origin + '/video/' + videoId
});
} else {
navigator.clipboard.writeText(window.location.origin + '/video/' + videoId);
alert('تم نسخ الرابط');
}
};
window.openComments = function(videoId) {
commentsVideoId = videoId;
replyingTo = null;
commentsPanel.classList.remove('translate-y-full');
commentsPanel.classList.add('translate-y-0');
loadComments(videoId);
};
window.closeComments = function() {
commentsPanel.classList.add('translate-y-full');
commentsPanel.classList.remove('translate-y-0');
commentsVideoId = null;
};
function loadComments(videoId) {
fetch(`/api/comments/${videoId}`)
.then(res => res.json())
.then(data => {
commentsList.innerHTML = '';
if (data.comments && data.comments.length) {
data.comments.forEach(c => {
commentsList.appendChild(renderComment(c));
});
} else {
commentsList.innerHTML = '<p class="text-center text-gray-500 py-8">لا توجد تعليقات بعد</p>';
}
});
}
function renderComment(c) {
const div = document.createElement('div');
div.className = 'border-b border-gray-800 pb-4';
div.innerHTML = `
<div class="flex items-center gap-2 mb-2">
<img src="${c.avatar_url}" class="w-8 h-8 rounded-full">
<span class="font-bold">@${c.username}</span>
<span class="text-xs text-gray-500">${new Date(c.timestamp).toLocaleString('ar')}</span>
</div>
<p class="text-gray-300 mr-10">${c.comment_text}</p>
<div class="flex gap-4 mt-2 mr-10">
<button onclick="likeComment(${c.id})" class="text-sm text-gray-400 hover:text-[#ff2d55] flex items-center gap-1">
❤️ <span>${c.likes_count || 0}</span>
</button>
<button onclick="replyToComment(${c.id})" class="text-sm text-gray-400 hover:text-[#ff2d55]">
↩️ رد
</button>
</div>
${c.replies && c.replies.length ? `
<div class="mr-6 mt-2 space-y-2">
${c.replies.map(r => renderComment(r)).map(el => el.outerHTML).join('')}
</div>
` : ''}
`;
return div;
}
window.likeComment = function(commentId) {
fetch(`/api/comment/like/${commentId}`, { method: 'POST' })
.then(() => { if (commentsVideoId) loadComments(commentsVideoId); });
};
window.replyToComment = function(parentId) {
replyingTo = parentId;
commentInput.focus();
commentInput.placeholder = 'اكتب رداً...';
};
window.sendComment = function() {
const text = commentInput.value.trim();
if (!text || !commentsVideoId) return;
fetch(`/api/comment/${commentsVideoId}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ comment: text, parent_id: replyingTo })
}).then(() => {
commentInput.value = '';
replyingTo = null;
commentInput.placeholder = 'أضف تعليقاً...';
loadComments(commentsVideoId);
});
};
function loadFeed(feed) {
currentFeed = feed;
currentPage = 1;
hasMore = true;
videoContainer.innerHTML = '';
loadVideos();
// تحديث العنصر النشط في القائمة
document.querySelectorAll('.nav-item, .nav-item-mobile').forEach(item => {
item.classList.remove('active', 'bg-[#ff2d55]');
});
event.target.closest('a').classList.add('active');
if (event.target.closest('a').classList.contains('nav-item')) {
event.target.closest('a').classList.add('bg-[#ff2d55]');
}
}
window.loadFeed = loadFeed;
function openGlass(url) {
fetch(url)
.then(res => res.text())
.then(html => {
glassContent.innerHTML = html;
glassLayer.style.display = 'block';
});
}
window.openGlass = openGlass;
function closeGlass() {
glassLayer.style.display = 'none';
}
window.closeGlass = closeGlass;
// إشعارات مباشرة
function connectNotifications() {
if (window.EventSource) {
eventSource = new EventSource('/api/notifications/stream');
eventSource.addEventListener('notification', function(e) {
const data = JSON.parse(e.data);
showNotification(data);
updateNotificationBadge();
});
eventSource.addEventListener('ping', function() {
// للحفاظ على الاتصال
});
}
}
function showNotification(notification) {
// إنشاء عنصر إشعار
const notif = document.createElement('div');
notif.className = 'fixed top-4 left-4 glass-sidebar rounded-2xl p-4 max-w-sm z-50 slide-up';
notif.innerHTML = `
<div class="flex gap-3">
<div class="text-2xl">${notification.type === 'like' ? '❤️' : notification.type === 'comment' ? '💬' : '🔔'}</div>
<div>
<p class="text-sm">${notification.content}</p>
<p class="text-xs text-gray-400 mt-1">الآن</p>
</div>
</div>
`;
document.body.appendChild(notif);
setTimeout(() => notif.remove(), 5000);
}
function updateNotificationBadge() {
fetch('/api/notifications/count')
.then(res => res.json())
.then(data => {
const badge = document.getElementById('sidebarNotifBadge');
if (data.count > 0) {
badge.textContent = data.count;
badge.classList.remove('hidden');
} else {
badge.classList.add('hidden');
}
});
}
function openNotifications() {
openGlass('/glass/notifications');
}
// بدء الاتصال المباشر للإشعارات
connectNotifications();
updateNotificationBadge();
setInterval(updateNotificationBadge, 30000);
</script>
</body>
</html>
'''
PROFILE_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@{{ user.username }} - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
.gradient-text {
background: linear-gradient(135deg, #ff2d55, #ff8a5c);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.btn-primary {
background: linear-gradient(135deg, #ff2d55, #ff4d6d);
}
.vip-badge {
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #000;
}
.gold-badge {
background: linear-gradient(135deg, #FFD700, #B8860B);
color: #000;
}
</style>
</head>
<body class="bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] min-h-screen text-white">
<!-- صورة الغلاف -->
<div class="h-64 bg-cover bg-center relative" style="background-image: url('/avatars/{{ user.cover_image or 'default_cover.jpg' }}')">
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/50 to-transparent"></div>
<a href="/" class="absolute top-4 right-4 glass-card px-4 py-2 rounded-2xl text-white hover:bg-[#ff2d55] transition z-10">
← العودة
</a>
</div>
<div class="max-w-6xl mx-auto px-4 -mt-20 relative z-20">
<!-- معلومات المستخدم -->
<div class="glass-card rounded-3xl p-6 mb-6">
<div class="flex flex-col md:flex-row items-start md:items-center gap-6">
<img src="/avatars/{{ user.avatar }}" class="w-28 h-28 rounded-3xl border-4 border-[#ff2d55] object-cover">
<div class="flex-1">
<div class="flex items-center gap-3 flex-wrap">
<h1 class="text-3xl font-bold">@{{ user.username }}</h1>
{% if user.is_verified %}
<span class="bg-[#ff2d55] px-3 py-1 rounded-full text-sm">✓ متحقق</span>
{% endif %}
{% if user.role == 'vip' %}
<span class="vip-badge px-3 py-1 rounded-full text-sm font-bold">VIP</span>
{% elif user.role == 'vip_gold' %}
<span class="gold-badge px-3 py-1 rounded-full text-sm font-bold">VIP GOLD</span>
{% endif %}
</div>
<p class="text-gray-300 mt-2">{{ user.bio or 'لا توجد سيرة ذاتية' }}</p>
<div class="flex gap-6 mt-4">
<div class="text-center">
<div class="text-2xl font-bold">{{ stats.videos or 0 }}</div>
<div class="text-sm text-gray-400">فيديو</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold">{{ followers_count }}</div>
<div class="text-sm text-gray-400">متابع</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold">{{ following_count }}</div>
<div class="text-sm text-gray-400">يتابع</div>
</div>
<div class="text-center">
<div class="text-2xl font-bold">{{ stats.total_views or 0 }}</div>
<div class="text-sm text-gray-400">مشاهدة</div>
</div>
</div>
<div class="flex items-center gap-4 mt-4">
<div class="flex items-center gap-2 bg-white/5 px-4 py-2 rounded-2xl">
<span class="text-[#ff2d55]">⭐</span>
<span>المستوى {{ stats.level }}</span>
</div>
<div class="flex items-center gap-2 bg-white/5 px-4 py-2 rounded-2xl">
<span class="text-[#ff2d55]">💎</span>
<span>{{ stats.xp }} XP</span>
</div>
<div class="flex items-center gap-2 bg-white/5 px-4 py-2 rounded-2xl">
<span class="text-[#ff2d55]">🪙</span>
<span>{{ stats.coins }} عملة</span>
</div>
</div>
</div>
<div class="flex gap-3">
{% if session.user_id == user.id %}
<a href="/settings" class="glass-card px-6 py-3 rounded-2xl hover:bg-[#ff2d55] transition">
⚙️ الإعدادات
</a>
<a href="/glass/saved" onclick="openGlass('/glass/saved'); return false;" class="glass-card px-6 py-3 rounded-2xl hover:bg-[#ff2d55] transition">
📁 المحفوظة
</a>
{% else %}
<button onclick="toggleFollow({{ user.id }})" class="glass-card px-8 py-3 rounded-2xl hover:bg-[#ff2d55] transition font-bold" id="followBtn">
{{ 'إلغاء المتابعة' if is_following else 'متابعة' }}
</button>
<button onclick="openChat({{ user.id }})" class="glass-card px-6 py-3 rounded-2xl hover:bg-[#ff2d55] transition">
💬 مراسلة
</button>
{% endif %}
</div>
</div>
</div>
<!-- إنجازات المستخدم -->
{% if stats.achievements %}
<div class="glass-card rounded-3xl p-6 mb-6">
<h2 class="text-xl font-bold mb-4">🏆 الإنجازات</h2>
<div class="flex gap-3 flex-wrap">
{% for achievement in stats.achievements %}
<span class="bg-[#ff2d55]/20 border border-[#ff2d55] px-4 py-2 rounded-2xl text-sm">
{{ achievement }}
</span>
{% endfor %}
</div>
</div>
{% endif %}
<!-- فيديوهات المستخدم -->
<div class="glass-card rounded-3xl p-6">
<h2 class="text-xl font-bold mb-4">📹 فيديوهات @{{ user.username }}</h2>
{% if videos %}
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{% for video in videos %}
<a href="/video/{{ video.id }}" class="group relative aspect-[9/16] rounded-2xl overflow-hidden">
<video src="{{ video.url }}" class="w-full h-full object-cover group-hover:scale-105 transition duration-300"></video>
<div class="absolute inset-0 bg-gradient-to-t from-black via-transparent to-transparent"></div>
<div class="absolute bottom-2 right-2 left-2">
<p class="text-sm font-bold truncate">{{ video.title or 'بلا عنوان' }}</p>
<div class="flex gap-2 text-xs text-gray-300 mt-1">
<span>❤️ {{ video.likes_count }}</span>
<span>👁️ {{ video.views }}</span>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<p class="text-center text-gray-400 py-12">لا توجد فيديوهات بعد</p>
{% endif %}
</div>
</div>
<script>
function toggleFollow(userId) {
const btn = document.getElementById('followBtn');
const isFollowing = btn.innerText.includes('إلغاء');
fetch((isFollowing ? '/unfollow/' : '/follow/') + userId, { method: 'POST' })
.then(res => res.json())
.then(data => {
if (data.status === 'ok') {
btn.innerText = isFollowing ? 'متابعة' : 'إلغاء المتابعة';
location.reload();
}
});
}
function openChat(userId) {
window.location.href = '/messages/' + userId;
}
function openGlass(url) {
window.location.href = url;
}
</script>
</body>
</html>
'''
SETTINGS_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>الإعدادات - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
.btn-primary {
background: linear-gradient(135deg, #ff2d55, #ff4d6d);
}
.input-glass {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.input-glass:focus {
border-color: #ff2d55;
box-shadow: 0 0 0 2px rgba(255, 45, 85, 0.2);
}
</style>
</head>
<body class="bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] min-h-screen text-white p-4">
<div class="max-w-4xl mx-auto">
<a href="/profile/{{ session.user_id }}" class="inline-block glass-card px-4 py-2 rounded-2xl mb-6 hover:bg-[#ff2d55] transition">
← العودة للملف الشخصي
</a>
<div class="glass-card rounded-3xl p-8">
<h1 class="text-3xl font-bold mb-8 text-center gradient-text">الإعدادات</h1>
<form method="post" enctype="multipart/form-data" class="space-y-8">
<!-- الصور الشخصية -->
<div class="flex flex-col items-center gap-4">
<div class="flex gap-6 flex-wrap justify-center">
<div class="text-center">
<img src="/avatars/{{ user.avatar }}" id="avatarPreview" class="w-32 h-32 rounded-3xl border-4 border-[#ff2d55] object-cover mb-2">
<label class="cursor-pointer glass-card px-4 py-2 rounded-2xl text-sm hover:bg-[#ff2d55] transition">
تغيير الصورة
<input type="file" name="avatar" accept="image/*" class="hidden" onchange="previewImage(this, 'avatarPreview')">
</label>
</div>
<div class="text-center">
<img src="/avatars/{{ user.cover_image or 'default_cover.jpg' }}" id="coverPreview" class="w-48 h-32 rounded-2xl border-4 border-[#ff2d55] object-cover mb-2">
<label class="cursor-pointer glass-card px-4 py-2 rounded-2xl text-sm hover:bg-[#ff2d55] transition">
تغيير الغلاف
<input type="file" name="cover" accept="image/*" class="hidden" onchange="previewImage(this, 'coverPreview')">
</label>
</div>
</div>
</div>
<!-- المعلومات الشخصية -->
<div class="grid md:grid-cols-2 gap-6">
<div>
<label class="block text-gray-300 mb-2">السيرة الذاتية</label>
<textarea name="bio" rows="3" class="w-full input-glass rounded-2xl p-3 text-white">{{ user.bio or '' }}</textarea>
</div>
<div>
<label class="block text-gray-300 mb-2">رقم الهاتف</label>
<input type="tel" name="phone" value="{{ user.phone or '' }}" class="w-full input-glass rounded-2xl p-3 text-white">
</div>
<div>
<label class="block text-gray-300 mb-2">الجنس</label>
<select name="gender" class="w-full input-glass rounded-2xl p-3 text-white">
<option value="">اختر</option>
<option value="male" {% if user.gender == 'male' %}selected{% endif %}>ذكر</option>
<option value="female" {% if user.gender == 'female' %}selected{% endif %}>أنثى</option>
</select>
</div>
<div>
<label class="block text-gray-300 mb-2">الدولة</label>
<input type="text" name="country" value="{{ user.country or '' }}" class="w-full input-glass rounded-2xl p-3 text-white">
</div>
<div>
<label class="block text-gray-300 mb-2">تاريخ الميلاد</label>
<input type="date" name="birth_date" value="{{ user.birth_date or '' }}" class="w-full input-glass rounded-2xl p-3 text-white">
</div>
</div>
<!-- إعدادات الخصوصية -->
<div class="border-t border-gray-800 pt-6">
<h2 class="text-xl font-bold mb-4">🔒 الخصوصية</h2>
<div class="grid md:grid-cols-3 gap-4">
<div>
<label class="block text-gray-300 mb-2">الملف الشخصي</label>
<select name="privacy_profile" class="w-full input-glass rounded-2xl p-3">
<option value="public" {% if settings.privacy_profile == 'public' %}selected{% endif %}>عام</option>
<option value="followers" {% if settings.privacy_profile == 'followers' %}selected{% endif %}>المتابعون فقط</option>
<option value="private" {% if settings.privacy_profile == 'private' %}selected{% endif %}>خاص</option>
</select>
</div>
<div>
<label class="block text-gray-300 mb-2">الفيديوهات</label>
<select name="privacy_videos" class="w-full input-glass rounded-2xl p-3">
<option value="public" {% if settings.privacy_videos == 'public' %}selected{% endif %}>عام</option>
<option value="followers" {% if settings.privacy_videos == 'followers' %}selected{% endif %}>المتابعون فقط</option>
<option value="private" {% if settings.privacy_videos == 'private' %}selected{% endif %}>خاص</option>
</select>
</div>
<div>
<label class="block text-gray-300 mb-2">الإعجابات</label>
<select name="privacy_likes" class="w-full input-glass rounded-2xl p-3">
<option value="public" {% if settings.privacy_likes == 'public' %}selected{% endif %}>عام</option>
<option value="followers" {% if settings.privacy_likes == 'followers' %}selected{% endif %}>المتابعون فقط</option>
<option value="private" {% if settings.privacy_likes == 'private' %}selected{% endif %}>خاص</option>
</select>
</div>
</div>
</div>
<!-- إعدادات الإشعارات -->
<div class="border-t border-gray-800 pt-6">
<h2 class="text-xl font-bold mb-4">🔔 الإشعارات</h2>
<div class="grid md:grid-cols-2 gap-4">
<label class="flex items-center gap-2">
<input type="checkbox" name="notifications_likes" {% if settings.notifications_likes %}checked{% endif %} class="form-checkbox text-[#ff2d55]">
<span>الإعجابات</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="notifications_comments" {% if settings.notifications_comments %}checked{% endif %} class="form-checkbox text-[#ff2d55]">
<span>التعليقات</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="notifications_follows" {% if settings.notifications_follows %}checked{% endif %} class="form-checkbox text-[#ff2d55]">
<span>المتابعات</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="notifications_messages" {% if settings.notifications_messages %}checked{% endif %} class="form-checkbox text-[#ff2d55]">
<span>الرسائل</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="notifications_live" {% if settings.notifications_live %}checked{% endif %} class="form-checkbox text-[#ff2d55]">
<span>البث المباشر</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="notifications_gifts" {% if settings.notifications_gifts %}checked{% endif %} class="form-checkbox text-[#ff2d55]">
<span>الهدايا</span>
</label>
</div>
</div>
<!-- التفضيلات -->
<div class="border-t border-gray-800 pt-6">
<h2 class="text-xl font-bold mb-4">⚙️ التفضيلات</h2>
<div class="grid md:grid-cols-2 gap-6">
<div>
<label class="block text-gray-300 mb-2">الوضع الليلي</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="dark_mode" {% if settings.dark_mode %}checked{% endif %} class="form-checkbox text-[#ff2d55]">
<span>تفعيل</span>
</label>
</div>
<div>
<label class="block text-gray-300 mb-2">اللغة</label>
<select name="language" class="w-full input-glass rounded-2xl p-3">
<option value="ar" {% if settings.language == 'ar' %}selected{% endif %}>العربية</option>
<option value="en" {% if settings.language == 'en' %}selected{% endif %}>English</option>
</select>
</div>
<div>
<label class="block text-gray-300 mb-2">لغة المحتوى</label>
<select name="content_language" class="w-full input-glass rounded-2xl p-3">
<option value="ar" {% if settings.content_language == 'ar' %}selected{% endif %}>العربية</option>
<option value="en" {% if settings.content_language == 'en' %}selected{% endif %}>English</option>
<option value="all" {% if settings.content_language == 'all' %}selected{% endif %}>الكل</option>
</select>
</div>
<div>
<label class="block text-gray-300 mb-2">التشغيل التلقائي</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="autoplay" {% if settings.autoplay %}checked{% endif %} class="form-checkbox text-[#ff2d55]">
<span>تفعيل</span>
</label>
</div>
<div>
<label class="block text-gray-300 mb-2">توفير البيانات</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="save_data" {% if settings.save_data %}checked{% endif %} class="form-checkbox text-[#ff2d55]">
<span>تفعيل</span>
</label>
</div>
<div>
<label class="block text-gray-300 mb-2">السماح بالتحميل</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="allow_download" {% if settings.allow_download %}checked{% endif %} class="form-checkbox text-[#ff2d55]">
<span>تفعيل</span>
</label>
</div>
</div>
</div>
<!-- الأمان -->
<div class="border-t border-gray-800 pt-6">
<h2 class="text-xl font-bold mb-4">🔐 الأمان</h2>
<a href="/2fa/setup" class="inline-block glass-card px-6 py-3 rounded-2xl hover:bg-[#ff2d55] transition">
إعداد المصادقة الثنائية
</a>
</div>
<button type="submit" class="w-full btn-primary text-white font-bold py-4 px-4 rounded-2xl text-lg">
حفظ التغييرات
</button>
</form>
</div>
</div>
<script>
function previewImage(input, previewId) {
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = e => document.getElementById(previewId).src = e.target.result;
reader.readAsDataURL(input.files[0]);
}
}
</script>
</body>
</html>
'''
UPLOAD_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>رفع فيديو - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
.btn-primary {
background: linear-gradient(135deg, #ff2d55, #ff4d6d);
}
.input-glass {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease;
}
.input-glass:focus {
border-color: #ff2d55;
box-shadow: 0 0 0 2px rgba(255, 45, 85, 0.2);
}
.upload-area {
border: 2px dashed rgba(255, 45, 85, 0.5);
transition: all 0.3s ease;
}
.upload-area:hover, .upload-area.dragover {
border-color: #ff2d55;
background: rgba(255, 45, 85, 0.1);
}
</style>
</head>
<body class="bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] min-h-screen text-white p-4">
<div class="max-w-4xl mx-auto">
<a href="/" class="inline-block glass-card px-4 py-2 rounded-2xl mb-6 hover:bg-[#ff2d55] transition">
← العودة للرئيسية
</a>
<div class="glass-card rounded-3xl p-8">
<h1 class="text-3xl font-bold mb-8 text-center gradient-text">رفع فيديو جديد</h1>
<!-- معاينة الكاميرا -->
<div class="mb-8">
<div class="relative">
<video id="cameraPreview" autoplay muted class="w-full rounded-3xl bg-black aspect-video object-cover hidden"></video>
<video id="recordedPreview" controls class="w-full rounded-3xl bg-black aspect-video object-cover hidden"></video>
<div id="cameraPlaceholder" class="upload-area rounded-3xl aspect-video flex flex-col items-center justify-center">
<div class="text-6xl mb-4">📸</div>
<p class="text-gray-400">اضغط لفتح الكاميرا أو اسحب الفيديو</p>
</div>
</div>
<div class="flex gap-3 mt-4 justify-center flex-wrap">
<button type="button" onclick="startCamera()" class="glass-card px-6 py-3 rounded-2xl hover:bg-[#ff2d55] transition">
📷 فتح الكاميرا
</button>
<button type="button" onclick="switchCamera()" class="glass-card px-6 py-3 rounded-2xl hover:bg-[#ff2d55] transition">
🔄 تبديل الكاميرا
</button>
<button type="button" onclick="startRecording()" id="startRecordBtn" class="glass-card px-6 py-3 rounded-2xl hover:bg-[#ff2d55] transition">
⏺ تسجيل
</button>
<button type="button" onclick="stopRecording()" id="stopRecordBtn" class="glass-card px-6 py-3 rounded-2xl hover:bg-[#ff2d55] transition" disabled>
⏹ إيقاف
</button>
</div>
</div>
<form method="post" enctype="multipart/form-data" id="uploadForm" class="space-y-6">
<div>
<label class="block text-gray-300 mb-2">عنوان الفيديو</label>
<input type="text" name="title" required class="w-full input-glass rounded-2xl p-3 text-white">
</div>
<div>
<label class="block text-gray-300 mb-2">وصف الفيديو</label>
<textarea name="description" rows="4" class="w-full input-glass rounded-2xl p-3 text-white" placeholder="أضف وصفاً مع وسوم #تحدي #مرح"></textarea>
</div>
<div class="grid md:grid-cols-2 gap-6">
<div>
<label class="block text-gray-300 mb-2">الموسيقى</label>
<select name="music_id" class="w-full input-glass rounded-2xl p-3 text-white">
<option value="">بدون موسيقى</option>
{% for m in music %}
<option value="{{ m.id }}">{{ m.title }} - {{ m.artist }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="block text-gray-300 mb-2">تحدي</label>
<select name="challenge_id" class="w-full input-glass rounded-2xl p-3 text-white">
<option value="">بدون تحدي</option>
{% for c in challenges %}
<option value="{{ c.id }}">{{ c.title }} (🏆 {{ c.prize_coins }} عملة)</option>
{% endfor %}
</select>
</div>
</div>
<div>
<label class="block text-gray-300 mb-2">إعدادات الخصوصية</label>
<select name="visibility" class="w-full input-glass rounded-2xl p-3 text-white">
<option value="public">عام</option>
<option value="friends">الأصدقاء فقط</option>
<option value="private">خاص</option>
</select>
</div>
<div class="grid md:grid-cols-3 gap-4">
<label class="flex items-center gap-2">
<input type="checkbox" name="allow_comments" checked class="form-checkbox text-[#ff2d55]">
<span>السماح بالتعليقات</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="allow_duet" checked class="form-checkbox text-[#ff2d55]">
<span>السماح بالثنائي</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" name="allow_stitch" checked class="form-checkbox text-[#ff2d55]">
<span>السماح بالمونتاج</span>
</label>
</div>
<div class="upload-area rounded-3xl p-8 text-center" id="fileDropArea">
<input type="file" name="video" accept="video/*" id="videoFile" class="hidden" onchange="handleFileSelect(this)">
<div class="text-4xl mb-2">🎬</div>
<p class="text-gray-400 mb-2">اسحب الفيديو هنا أو</p>
<button type="button" onclick="document.getElementById('videoFile').click()" class="btn-primary text-white px-6 py-2 rounded-2xl">
اختر ملف
</button>
<p id="fileName" class="text-sm text-gray-500 mt-2"></p>
</div>
<button type="submit" class="w-full btn-primary text-white font-bold py-4 px-4 rounded-2xl text-lg">
رفع الفيديو
</button>
</form>
</div>
</div>
<script>
let currentStream = null;
let useFrontCamera = true;
let mediaRecorder = null;
let recordedChunks = [];
const cameraPreview = document.getElementById('cameraPreview');
const recordedPreview = document.getElementById('recordedPreview');
const cameraPlaceholder = document.getElementById('cameraPlaceholder');
const startRecordBtn = document.getElementById('startRecordBtn');
const stopRecordBtn = document.getElementById('stopRecordBtn');
async function startCamera() {
try {
if (currentStream) {
currentStream.getTracks().forEach(track => track.stop());
}
const constraints = {
video: { facingMode: useFrontCamera ? 'user' : 'environment' },
audio: true
};
currentStream = await navigator.mediaDevices.getUserMedia(constraints);
cameraPreview.srcObject = currentStream;
cameraPreview.classList.remove('hidden');
recordedPreview.classList.add('hidden');
cameraPlaceholder.classList.add('hidden');
} catch (err) {
alert('لا يمكن الوصول للكاميرا: ' + err.message);
}
}
function switchCamera() {
useFrontCamera = !useFrontCamera;
startCamera();
}
function startRecording() {
if (!currentStream) {
alert('يرجى تشغيل الكاميرا أولاً');
return;
}
recordedChunks = [];
mediaRecorder = new MediaRecorder(currentStream);
mediaRecorder.ondataavailable = event => {
if (event.data.size > 0) recordedChunks.push(event.data);
};
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: 'video/mp4' });
const url = URL.createObjectURL(blob);
recordedPreview.src = url;
recordedPreview.classList.remove('hidden');
cameraPreview.classList.add('hidden');
// إنشاء ملف للرفع
const file = new File([blob], 'recording.mp4', { type: 'video/mp4' });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
document.getElementById('videoFile').files = dataTransfer.files;
document.getElementById('fileName').textContent = 'recording.mp4';
};
mediaRecorder.start();
startRecordBtn.disabled = true;
stopRecordBtn.disabled = false;
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
startRecordBtn.disabled = false;
stopRecordBtn.disabled = true;
}
}
function handleFileSelect(input) {
if (input.files && input.files[0]) {
document.getElementById('fileName').textContent = input.files[0].name;
// معاينة الفيديو
const url = URL.createObjectURL(input.files[0]);
recordedPreview.src = url;
recordedPreview.classList.remove('hidden');
cameraPreview.classList.add('hidden');
cameraPlaceholder.classList.add('hidden');
}
}
// سحب وإفلات
const dropArea = document.getElementById('fileDropArea');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, () => dropArea.classList.add('dragover'), false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, () => dropArea.classList.remove('dragover'), false);
});
dropArea.addEventListener('drop', (e) => {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length > 0) {
document.getElementById('videoFile').files = files;
handleFileSelect(document.getElementById('videoFile'));
}
});
</script>
</body>
</html>
'''
SEARCH_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>بحث - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
.input-glass {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
}
</style>
</head>
<body class="bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] min-h-screen text-white p-4">
<div class="max-w-6xl mx-auto">
<a href="/" class="inline-block glass-card px-4 py-2 rounded-2xl mb-6 hover:bg-[#ff2d55] transition">
← العودة للرئيسية
</a>
<div class="glass-card rounded-3xl p-6 mb-6">
<div class="flex gap-4">
<input type="text" id="searchInput" placeholder="ابحث عن فيديوهات، مستخدمين، هاشتاغات..."
class="flex-1 input-glass rounded-2xl px-6 py-4 text-white text-lg focus:outline-none focus:ring-2 focus:ring-[#ff2d55]"
value="{{ request.args.get('q', '') }}">
<button onclick="performSearch()" class="btn-primary px-8 py-4 rounded-2xl font-bold">
بحث
</button>
</div>
<div class="flex gap-2 mt-4">
<button onclick="setType('videos')" class="px-4 py-2 rounded-2xl bg-white/5 hover:bg-[#ff2d55] transition" id="typeVideos">📹 فيديوهات</button>
<button onclick="setType('users')" class="px-4 py-2 rounded-2xl bg-white/5 hover:bg-[#ff2d55] transition" id="typeUsers">👤 مستخدمين</button>
<button onclick="setType('hashtags')" class="px-4 py-2 rounded-2xl bg-white/5 hover:bg-[#ff2d55] transition" id="typeHashtags"># هاشتاغات</button>
</div>
</div>
<div id="results" class="space-y-6"></div>
<div id="loading" class="text-center py-8 hidden">
<div class="inline-block animate-spin text-4xl">⏳</div>
</div>
</div>
<script>
let currentType = 'videos';
let currentPage = 1;
let currentQuery = '';
let loading = false;
document.getElementById('searchInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') performSearch();
});
function setType(type) {
currentType = type;
document.querySelectorAll('[id^="type"]').forEach(btn => {
btn.classList.remove('bg-[#ff2d55]');
});
document.getElementById(`type${type.charAt(0).toUpperCase() + type.slice(1)}`).classList.add('bg-[#ff2d55]');
performSearch();
}
function performSearch(reset = true) {
const query = document.getElementById('searchInput').value.trim();
if (!query) return;
currentQuery = query;
if (reset) {
currentPage = 1;
document.getElementById('results').innerHTML = '';
}
loadResults();
}
function loadResults() {
if (loading) return;
loading = true;
document.getElementById('loading').classList.remove('hidden');
fetch(`/api/search?q=${encodeURIComponent(currentQuery)}&type=${currentType}&page=${currentPage}`)
.then(res => res.json())
.then(data => {
document.getElementById('loading').classList.add('hidden');
if (currentType === 'videos') {
renderVideos(data.videos);
} else if (currentType === 'users') {
renderUsers(data.users);
} else if (currentType === 'hashtags') {
renderHashtags(data.hashtags);
}
if (data.next_page) {
currentPage = data.next_page;
}
loading = false;
});
}
function renderVideos(videos) {
const container = document.getElementById('results');
if (videos.length === 0 && currentPage === 1) {
container.innerHTML = '<p class="text-center text-gray-400 py-12">لا توجد نتائج</p>';
return;
}
const grid = document.createElement('div');
grid.className = 'grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4';
videos.forEach(v => {
const item = document.createElement('a');
item.href = `/video/${v.id}`;
item.className = 'group relative aspect-[9/16] rounded-2xl overflow-hidden';
item.innerHTML = `
<video src="${v.url}" class="w-full h-full object-cover group-hover:scale-105 transition"></video>
<div class="absolute inset-0 bg-gradient-to-t from-black via-transparent to-transparent"></div>
<div class="absolute bottom-2 right-2 left-2">
<div class="flex items-center gap-2">
<img src="${v.avatar_url}" class="w-6 h-6 rounded-full border border-[#ff2d55]">
<span class="text-sm">@${v.username}</span>
</div>
<div class="flex gap-2 text-xs mt-1">
<span>❤️ ${v.likes_count || 0}</span>
<span>👁️ ${v.views || 0}</span>
</div>
</div>
`;
grid.appendChild(item);
});
container.appendChild(grid);
}
function renderUsers(users) {
const container = document.getElementById('results');
if (users.length === 0 && currentPage === 1) {
container.innerHTML = '<p class="text-center text-gray-400 py-12">لا توجد نتائج</p>';
return;
}
users.forEach(u => {
const item = document.createElement('div');
item.className = 'glass-card rounded-2xl p-4 flex items-center gap-4';
item.innerHTML = `
<img src="${u.avatar_url}" class="w-16 h-16 rounded-2xl object-cover border-2 border-[#ff2d55]">
<div class="flex-1">
<div class="flex items-center gap-2">
<span class="font-bold">@${u.username}</span>
${u.is_verified ? '<span class="text-[#ff2d55]">✓</span>' : ''}
</div>
<p class="text-sm text-gray-400">${u.bio || ''}</p>
<div class="text-xs text-gray-500 mt-1">${u.total_followers || 0} متابع</div>
</div>
<button onclick="window.location.href='/profile/${u.id}'" class="glass-card px-4 py-2 rounded-2xl hover:bg-[#ff2d55] transition">
عرض
</button>
`;
container.appendChild(item);
});
}
function renderHashtags(hashtags) {
const container = document.getElementById('results');
if (hashtags.length === 0 && currentPage === 1) {
container.innerHTML = '<p class="text-center text-gray-400 py-12">لا توجد نتائج</p>';
return;
}
const grid = document.createElement('div');
grid.className = 'grid md:grid-cols-2 lg:grid-cols-3 gap-4';
hashtags.forEach(h => {
const item = document.createElement('a');
item.href = `/search?q=%23${h.tag}`;
item.className = 'glass-card rounded-2xl p-6 text-center hover:border-[#ff2d55] transition';
item.innerHTML = `
<div class="text-4xl mb-2">#</div>
<div class="text-xl font-bold text-[#ff2d55]">#${h.tag}</div>
<div class="text-sm text-gray-400 mt-2">${h.usage_count || 0} فيديو</div>
<div class="text-xs text-gray-500">${h.total_views || 0} مشاهدة</div>
`;
grid.appendChild(item);
});
container.appendChild(grid);
}
// التمرير اللانهائي
window.addEventListener('scroll', () => {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100) {
if (currentQuery && !loading) {
loadResults();
}
}
});
// تنفيذ البحث التلقائي إذا كان هناك استعلام في URL
const urlParams = new URLSearchParams(window.location.search);
const query = urlParams.get('q');
if (query) {
document.getElementById('searchInput').value = query;
performSearch();
}
</script>
</body>
</html>
'''
LIVE_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>البث المباشر - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
.live-badge {
background: #ff2d55;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
</style>
</head>
<body class="bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] min-h-screen text-white p-4">
<div class="max-w-6xl mx-auto">
<a href="/" class="inline-block glass-card px-4 py-2 rounded-2xl mb-6 hover:bg-[#ff2d55] transition">
← العودة للرئيسية
</a>
<div class="flex justify-between items-center mb-8">
<h1 class="text-3xl font-bold gradient-text">البث المباشر</h1>
<button onclick="startLive()" class="btn-primary px-8 py-3 rounded-2xl font-bold flex items-center gap-2">
<span class="text-2xl">🎥</span>
بدء بث مباشر
</button>
</div>
<div id="streams" class="grid md:grid-cols-2 lg:grid-cols-3 gap-6"></div>
</div>
<script>
function loadStreams() {
fetch('/api/live/active')
.then(res => res.json())
.then(streams => {
const container = document.getElementById('streams');
if (streams.length === 0) {
container.innerHTML = `
<div class="col-span-full text-center py-12">
<div class="text-6xl mb-4">📡</div>
<p class="text-gray-400">لا توجد بثوث مباشرة حالياً</p>
</div>
`;
return;
}
container.innerHTML = streams.map(s => `
<div class="glass-card rounded-3xl overflow-hidden cursor-pointer hover:border-[#ff2d55] transition" onclick="window.location.href='/live/${s.id}'">
<div class="relative aspect-video bg-gradient-to-br from-[#ff2d55] to-[#ff8a5c]">
<div class="absolute top-2 right-2 live-badge text-white px-3 py-1 rounded-full text-sm font-bold flex items-center gap-1">
<span class="w-2 h-2 bg-white rounded-full"></span>
مباشر
</div>
<div class="absolute bottom-2 left-2 bg-black/60 px-2 py-1 rounded-full text-sm flex items-center gap-1">
<span>👁️</span>
<span>${s.viewers}</span>
</div>
</div>
<div class="p-4">
<div class="flex items-center gap-3 mb-2">
<img src="/avatars/${s.avatar}" class="w-10 h-10 rounded-full border-2 border-[#ff2d55]">
<div>
<div class="font-bold">@${s.username}</div>
<div class="text-sm text-gray-400">${s.title || 'بث مباشر'}</div>
</div>
</div>
</div>
</div>
`).join('');
});
}
function startLive() {
const title = prompt('أدخل عنوان البث:');
if (!title) return;
fetch('/api/live/start', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({title})
})
.then(res => res.json())
.then(data => {
if (data.status === 'ok') {
alert(`تم بدء البث!\n\nمفتاح البث: ${data.stream_key}\nخادم RTMP: ${data.rtmp_url}\n\nاستخدم هذه المعلومات في برنامج البث مثل OBS`);
loadStreams();
}
})
.catch(err => {
alert('فشل بدء البث. قد تحتاج عضوية VIP لهذه الميزة.');
});
}
loadStreams();
setInterval(loadStreams, 10000); // تحديث كل 10 ثوان
</script>
</body>
</html>
'''
LEADERBOARD_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>المتصدرين - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
.gold { color: #FFD700; }
.silver { color: #C0C0C0; }
.bronze { color: #CD7F32; }
</style>
</head>
<body class="bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] min-h-screen text-white p-4">
<div class="max-w-4xl mx-auto">
<a href="/" class="inline-block glass-card px-4 py-2 rounded-2xl mb-6 hover:bg-[#ff2d55] transition">
← العودة للرئيسية
</a>
<div class="glass-card rounded-3xl p-8">
<h1 class="text-3xl font-bold mb-8 text-center gradient-text">🏆 المتصدرين</h1>
<div class="flex gap-4 mb-8 justify-center">
<button onclick="loadLeaderboard('all')" class="px-6 py-2 rounded-2xl bg-[#ff2d55] text-white font-bold" id="btnAll">شامل</button>
<button onclick="loadLeaderboard('weekly')" class="px-6 py-2 rounded-2xl bg-white/5 hover:bg-[#ff2d55] transition" id="btnWeekly">أسبوعي</button>
<button onclick="loadLeaderboard('daily')" class="px-6 py-2 rounded-2xl bg-white/5 hover:bg-[#ff2d55] transition" id="btnDaily">يومي</button>
</div>
<div id="leaderboard" class="space-y-3"></div>
</div>
</div>
<script>
function loadLeaderboard(period) {
// تحديث الأزرار
['all', 'weekly', 'daily'].forEach(p => {
const btn = document.getElementById(`btn${p.charAt(0).toUpperCase() + p.slice(1)}`);
if (p === period) {
btn.classList.add('bg-[#ff2d55]');
btn.classList.remove('bg-white/5');
} else {
btn.classList.remove('bg-[#ff2d55]');
btn.classList.add('bg-white/5');
}
});
fetch(`/api/gamification/leaderboard?period=${period}`)
.then(res => res.json())
.then(data => {
const container = document.getElementById('leaderboard');
if (data.leaderboard.length === 0) {
container.innerHTML = '<p class="text-center text-gray-400 py-8">لا توجد بيانات</p>';
return;
}
let html = '';
data.leaderboard.forEach((user, index) => {
let medal = '';
if (index === 0) medal = '<span class="gold text-2xl">🥇</span>';
else if (index === 1) medal = '<span class="silver text-2xl">🥈</span>';
else if (index === 2) medal = '<span class="bronze text-2xl">🥉</span>';
html += `
<div class="flex items-center gap-4 p-4 glass-card rounded-2xl ${index === 0 ? 'border border-[#ff2d55]' : ''}">
<div class="w-8 text-center font-bold text-xl">${medal || (index + 1)}</div>
<img src="${user.avatar_url}" class="w-12 h-12 rounded-full border-2 border-[#ff2d55]">
<div class="flex-1">
<div class="flex items-center gap-2">
<span class="font-bold">@${user.username}</span>
${user.is_verified ? '<span class="text-[#ff2d55]">✓</span>' : ''}
</div>
<div class="text-sm text-gray-400">المستوى ${user.level || 1}</div>
</div>
<div class="text-left">
<div class="font-bold text-[#ff2d55]">${user.xp || user.daily_xp || user.weekly_xp || 0}</div>
<div class="text-xs text-gray-500">XP</div>
</div>
</div>
`;
});
if (data.user_rank) {
html += `
<div class="mt-6 pt-4 border-t border-gray-800">
<p class="text-center text-gray-400">ترتيبك الحالي: <span class="text-[#ff2d55] font-bold">#${data.user_rank}</span></p>
</div>
`;
}
container.innerHTML = html;
});
}
// تحميل المتصدرين الشامل افتراضياً
loadLeaderboard('all');
</script>
</body>
</html>
'''
GIFTS_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>الهدايا - ARC Video</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap');
* { font-family: 'Tajawal', sans-serif; }
.glass-card {
background: rgba(20, 20, 30, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 45, 85, 0.2);
}
</style>
</head>
<body class="bg-gradient-to-br from-[#0f0f0f] via-[#1a1a1a] to-[#0a0a0a] min-h-screen text-white p-4">
<div class="max-w-4xl mx-auto">
<a href="/" class="inline-block glass-card px-4 py-2 rounded-2xl mb-6 hover:bg-[#ff2d55] transition">
← العودة للرئيسية
</a>
<div class="glass-card rounded-3xl p-8">
<h1 class="text-3xl font-bold mb-8 text-center gradient-text">🎁 الهدايا الافتراضية</h1>
<div id="balance" class="text-center mb-8">
<p class="text-gray-400">رصيدك الحالي</p>
<div class="flex justify-center gap-4 mt-2">
<span class="text-2xl">🪙 <span id="coins">0</span></span>
<span class="text-2xl">💎 <span id="diamonds">0</span></span>
</div>
</div>
<div id="gifts" class="grid md:grid-cols-2 lg:grid-cols-3 gap-4"></div>
</div>
</div>
<script>
function loadBalance() {
fetch('/api/coins/balance')
.then(res => res.json())
.then(data => {
document.getElementById('coins').textContent = data.coins;
document.getElementById('diamonds').textContent = data.diamonds;
});
}
function loadGifts() {
fetch('/api/gifts')
.then(res => res.json())
.then(gifts => {
const container = document.getElementById('gifts');
container.innerHTML = gifts.map(g => `
<div class="glass-card rounded-2xl p-6 text-center hover:border-[#ff2d55] transition">
<div class="text-6xl mb-4">${g.animation_url ? '🎁' : '🎁'}</div>
<h3 class="font-bold text-lg mb-2">${g.name}</h3>
<p class="text-[#ff2d55] font-bold mb-4">🪙 ${g.price}</p>
<button onclick="buyGift(${g.id}, ${g.price})" class="w-full btn-primary px-4 py-2 rounded-2xl text-white">
إرسال
</button>
</div>
`).join('');
});
}
function buyGift(giftId, price) {
if (!confirm(`تأكيد شراء الهدية بقيمة ${price} عملة؟`)) return;
fetch('/api/live/gift/1', { // سيتم تحديد stream_id لاحقاً
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({gift_id: giftId})
})
.then(res => res.json())
.then(data => {
if (data.status === 'ok') {
alert('تم إرسال الهدية بنجاح');
loadBalance();
} else {
alert(data.error || 'فشل إرسال الهدية');
}
});
}
loadBalance();
loadGifts();
</script>
</body>
</html>
'''
# ==================== دوال Cloudinary ====================
def upload_video_to_cloudinary(file_path, public_id=None):
"""رفع فيديو إلى Cloudinary مع تحسينات"""
try:
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": 600, "height": 600, "crop": "pad", "audio_codec": "none"},
{"width": 1080, "height": 1920, "crop": "pad", "quality": "auto"}
],
eager_async=True,
chunk_size=6000000,
timeout=120
)
return response.get('secure_url'), response.get('public_id')
except Exception as e:
logger.error(f"خطأ في رفع Cloudinary: {e}")
return None, None
def generate_video_thumbnail(video_public_id):
"""توليد رابط الصورة المصغرة من Cloudinary"""
return cloudinary.CloudinaryVideo(video_public_id).video_thumbnail_url(
width=400, height=400, crop="fill", quality="auto"
)
def add_watermark_to_cloudinary_video(public_id, text="ARC"):
"""إنشاء رابط فيديو مع علامة مائية نصية"""
transformed_url = cloudinary.CloudinaryVideo(public_id).video_url(
transformation=[
{"width": 1080, "height": 1920, "crop": "limit"},
{"overlay": {"font_family": "Arial", "font_size": 60, "text": text},
"gravity": "south_east", "opacity": 50, "x": 20, "y": 20}
]
)
return transformed_url
# ==================== دوال التشفير المتقدمة ====================
def encrypt_video_file(input_path, output_path=None, key=None):
"""تشفير ملف فيديو باستخدام AES-256-GCM"""
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 = get_random_bytes(32)
try:
with open(input_path, 'rb') as f:
data = f.read()
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(data)
# تخزين IV و tag مع البيانات المشفرة
with open(output_path, 'wb') as f:
f.write(cipher.nonce)
f.write(tag)
f.write(ciphertext)
return output_path, key
except Exception as e:
logger.error(f"خطأ في التشفير: {e}")
return None, None
def decrypt_video_file(encrypted_path, key, output_path=None):
"""فك تشفير فيديو"""
if not HAS_CRYPTO:
return None
if output_path is None:
output_path = encrypted_path.replace('.enc', '')
try:
with open(encrypted_path, 'rb') as f:
nonce = f.read(16)
tag = f.read(16)
ciphertext = f.read()
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
data = cipher.decrypt_and_verify(ciphertext, tag)
with open(output_path, 'wb') as f:
f.write(data)
return output_path
except Exception as e:
logger.error(f"خطأ في فك التشفير: {e}")
return None
def add_watermark_to_video(input_path, output_path=None, watermark_text="ARC"):
"""إضافة علامة مائية نصية على الفيديو"""
if not HAS_CV2 or not HAS_PIL:
return None
if output_path is None:
filename = os.path.basename(input_path)
output_path = os.path.join(WATERMARK_FOLDER, f"watermarked_{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
text_size = cv2.getTextSize(watermark_text, font, 1, 2)[0]
text_x = width - text_size[0] - 20
text_y = height - 20
frame_count = 0
while True:
ret, frame = cap.read()
if not ret:
break
# إضافة العلامة المائية الشفافة
overlay = frame.copy()
cv2.putText(overlay, watermark_text, (text_x, text_y), font, 1, (255, 255, 255), 2, cv2.LINE_AA)
cv2.putText(overlay, f"ARC {datetime.now().year}", (20, 40), font, 0.7, (255, 45, 85), 2)
# دمج مع الشفافية
cv2.addWeighted(overlay, 0.7, frame, 0.3, 0, frame)
out.write(frame)
frame_count += 1
if frame_count % 100 == 0:
logger.info(f"تقدم العلامة المائية: {frame_count}/{total_frames}")
cap.release()
out.release()
cv2.destroyAllWindows()
return output_path
except Exception as e:
logger.error(f"خطأ في إضافة العلامة المائية: {e}")
return None
def extract_video_features(video_path):
"""استخراج خصائص من الفيديو لتوليد متجه"""
if not HAS_CV2:
return random_vector()
try:
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return random_vector()
# استخراج عدة إطارات
frames = []
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
sample_indices = [int(i * total_frames / 5) for i in range(5)]
for idx in sample_indices:
cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
ret, frame = cap.read()
if ret:
# تحويل إلى تدرج رمادي وتصغير
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
resized = cv2.resize(gray, (32, 32))
frames.append(resized.flatten())
cap.release()
if not frames:
return random_vector()
# متوسط الإطارات
features = np.mean(frames, axis=0).astype(np.float32)
# تطبيع
features = features / 255.0
# تقليل الأبعاد
if len(features) > VECTOR_DIM:
step = len(features) // VECTOR_DIM
features = features[::step][:VECTOR_DIM]
elif len(features) < VECTOR_DIM:
features = np.pad(features, (0, VECTOR_DIM - len(features)))
return features.tobytes()
except Exception as e:
logger.error(f"خطأ في استخراج الخصائص: {e}")
return random_vector()
# ==================== التشغيل ====================
if __name__ == '__main__':
with app.app_context():
try:
init_db()
logger.info("✅ تم تهيئة قاعدة البيانات بنجاح")
print("✅ ARC Video Global - النسخة المتقدمة جاهزة للتشغيل")
print("=" * 60)
print("🚀 منصة ARC Video - إمبراطورية الفيديو العالمية")
print("📱 Glassmorphism UI - نظام توصيات ذكي - أمان متقدم")
print("💎 عضوية VIP - هدايا افتراضية - بث مباشر")
print("=" * 60)
except Exception as e:
logger.error(f"❌ خطأ أثناء التهيئة: {e}")
print(f"❌ خطأ: {e}")
app.run(host='0.0.0.0', port=7860, debug=True, threaded=True)