Shalaby-VIP-Panel / src /streamlit_app.py
ipnshalaby5's picture
Update src/streamlit_app.py
a8fd27f verified
Raw
History Blame Contribute Delete
82.7 kB
import streamlit as st
import telebot
import requests
import json
import os
import re
import urllib3
import threading
import time
import uuid
import asyncio
import random
from datetime import datetime
from urllib.parse import urlparse, parse_qs
import urllib.request
from huggingface_hub import HfApi, hf_hub_download
import streamlit.components.v1 as components
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from concurrent.futures import ThreadPoolExecutor
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# ==========================================
# ⚙️ الإعدادات الأساسية
# ==========================================
TOKEN = "8622568429:AAF9Dj535-JSt-wMQA5QMSPvOAM8SBbiL8I"
bot = telebot.TeleBot(TOKEN)
HF_TOKEN = "Hf_NrYBkhFdfJTFGIdxGbAzuLICxgvxWcIrHn"
REPO_ID = "Ipnshalaby5/IPTV_DB"
api = HfApi(token=HF_TOKEN)
if 'logged_in' not in st.session_state:
st.session_state.logged_in = False
if 'username' not in st.session_state:
st.session_state.username = ""
if 'reset_exp' not in st.session_state:
st.session_state.reset_exp = 0
if "user_auth" in st.query_params and not st.session_state.logged_in:
st.session_state.username = st.query_params["user_auth"]
st.session_state.logged_in = True
if not st.session_state.logged_in and "user_auth" not in st.query_params:
components.html("""<script>const savedUser = localStorage.getItem("user_auth"); if(savedUser){const url = new URL(window.location.href); url.searchParams.set("user_auth", savedUser); window.location.href = url.href;}</script>""", height=0, width=0)
def get_user_db_file():
return f"master_database_{st.session_state.username}.json"
def get_user_servers_file():
return f"servers_database_{st.session_state.username}.json"
ADMIN_FILE = "admin_chat.txt"
VIP_LOGOS = {
'bein': 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/BeIN_Sports_logo.svg/1024px-BeIN_Sports_logo.svg.png',
'ssc': 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/87/SSC_Sports_Logo.png/800px-SSC_Sports_Logo.png',
'mbc': 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/MBC_logo.svg/512px-MBC_logo.svg.png',
'ad sport': 'https://upload.wikimedia.org/wikipedia/en/thumb/0/05/AD_Sports_Logo.svg/1200px-AD_Sports_Logo.svg.png',
'ontime': 'https://upload.wikimedia.org/wikipedia/commons/thumb/7/7b/OnTime_Sports_Logo.png/800px-OnTime_Sports_Logo.png',
'alkass': 'https://upload.wikimedia.org/wikipedia/en/thumb/2/2b/Al_Kass_Sports_Channels_Logo.svg/1200px-Al_Kass_Sports_Channels_Logo.svg.png',
'fajr': 'https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Al_Fajr_TV_Logo.png/800px-Al_Fajr_TV_Logo.png',
'dubai': 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Dubai_Sports_logo.svg/512px-Dubai_Sports_logo.svg.png'
}
def get_vip_logo(channel_name):
name_lower = channel_name.lower()
for key, logo_url in VIP_LOGOS.items():
if key in name_lower: return logo_url
return ""
def sync_file_from_cloud(file_name):
if f"synced_{file_name}" not in st.session_state:
try:
hf_hub_download(repo_id=REPO_ID, filename=file_name, repo_type="dataset", token=HF_TOKEN, local_dir=".", force_download=True)
st.session_state[f"synced_{file_name}"] = True
except: pass
def sync_file_to_cloud(file_name):
try:
api.upload_file(path_or_fileobj=file_name, path_in_repo=file_name, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
except: pass
def load_global_vips():
sync_file_from_cloud("global_vips.json")
if os.path.exists("global_vips.json"):
try:
with open("global_vips.json", 'r', encoding='utf-8') as f: return json.load(f)
except: return []
return []
def save_to_global_vips(server_data):
vips = load_global_vips()
uid_check = lambda s: s.get('host') == server_data.get('host') and s.get('user') == server_data.get('user') and s.get('mac') == server_data.get('mac')
if not any(uid_check(s) for s in vips):
vips.append(server_data)
with open("global_vips.json", 'w', encoding='utf-8') as f: json.dump(vips, f, indent=4)
sync_file_to_cloud("global_vips.json")
def load_users_registry():
registry_file = "all_users_registry.json"
sync_file_from_cloud(registry_file)
if os.path.exists(registry_file):
try:
with open(registry_file, 'r', encoding='utf-8') as f: return json.load(f)
except: return []
return []
def save_users_registry(users_list):
registry_file = "all_users_registry.json"
with open(registry_file, 'w', encoding='utf-8') as f: json.dump(users_list, f)
sync_file_to_cloud(registry_file)
def parse_timestamp(ts):
if not ts or str(ts).strip() == "": return "Unlimited"
ts_str = str(ts).strip()
if ts_str.lower() in ['null', 'unlimited', 'none', '0']: return "Unlimited"
if "0000-00-00" in ts_str or "00:00:00 00-00-0000" in ts_str or "1970" in ts_str: return "Unlimited"
try:
if ts_str.isdigit():
return datetime.fromtimestamp(int(ts_str)).strftime('%Y-%m-%d')
if 'am' in ts_str.lower() or 'pm' in ts_str.lower():
return datetime.strptime(ts_str, "%B %d, %Y, %I:%M %p").strftime('%Y-%m-%d')
date_match = re.search(r'(\d{1,4}[-/\.]\d{1,2}[-/\.]\d{1,4})', ts_str)
if date_match:
return date_match.group(1).replace('.', '-').replace('/', '-')
return ts_str
except: return "Unlimited"
def calculate_days_left(exp_date_str_or_ts):
if not exp_date_str_or_ts: return "Unlimited"
ts_str = str(exp_date_str_or_ts).strip()
if ts_str.lower() in ['null', 'unlimited', 'none', '0', '']: return "Unlimited"
if "0000-00-00" in ts_str or "00:00:00 00-00-0000" in ts_str or "1970" in ts_str: return "Unlimited"
try:
if ts_str.isdigit():
exp = datetime.fromtimestamp(int(ts_str))
else:
ts_str_clean = ts_str.replace('.', '-').replace('/', '-')
try: exp = datetime.strptime(ts_str_clean, '%Y-%m-%d %H:%M:%S')
except:
try: exp = datetime.strptime(ts_str_clean, '%Y-%m-%d')
except:
try: exp = datetime.strptime(ts_str_clean, '%d-%m-%Y')
except: return "Unlimited"
return max(0, (exp - datetime.now()).days)
except: return "Unlimited"
def save_admin(chat_id):
with open(ADMIN_FILE, "w") as f: f.write(str(chat_id))
def load_servers():
file_name = get_user_servers_file()
sync_file_from_cloud(file_name)
if os.path.exists(file_name):
try:
with open(file_name, 'r', encoding='utf-8') as f: return json.load(f)
except: return []
return []
def save_server_to_db(host, user, pwd, days, auth_type='xtream', created_at="غير معروف", exp_date="غير معروف", mac=None, conns="N/A"):
servers = load_servers()
file_name = get_user_servers_file()
found = False
for s in servers:
if s.get('host') == host and s.get('user') == user and s.get('mac') == mac:
s.update({'pass': pwd, 'days': days, 'type': auth_type, 'created_at': created_at, 'exp_date': exp_date, 'status': '🟢 نشط', 'conns': conns})
found = True; break
if not found:
servers.append({'host': host, 'user': user, 'pass': pwd, 'days': days, 'type': auth_type, 'created_at': created_at, 'exp_date': exp_date, 'status': '🟢 نشط', 'mac': mac, 'conns': conns})
with open(file_name, 'w', encoding='utf-8') as f:
json.dump(servers, f, indent=4)
sync_file_to_cloud(file_name)
def save_all_servers_to_db(servers_list):
file_name = get_user_servers_file()
with open(file_name, 'w', encoding='utf-8') as f:
json.dump(servers_list, f, indent=4)
sync_file_to_cloud(file_name)
def load_db():
file_name = get_user_db_file()
sync_file_from_cloud(file_name)
if os.path.exists(file_name):
try:
with open(file_name, 'r', encoding='utf-8') as f: return json.load(f).get("channels", [])
except: return []
return []
def save_db(channels):
file_name = get_user_db_file()
with open(file_name, 'w', encoding='utf-8') as f:
json.dump({"channels": channels}, f, ensure_ascii=False, indent=4)
sync_file_to_cloud(file_name)
def export_to_m3u(channels, file_name="VLC_Playlist.m3u"):
lines = ["#EXTM3U", "# Generated by Shalaby VIP Tool\n"]
for ch in channels:
lines.append(f'#EXTINF:-1 tvg-id="{ch["id"]}" tvg-logo="{ch["logo"]}" group-title="{ch["group"]}",{ch["name"]}')
lines.append(f"{ch['url']}|User-Agent={ch['userAgent']}")
with open(file_name, 'w', encoding='utf-8') as f:
f.write("\n".join(lines))
return file_name
# ==========================================
# 🧠 دوال الترتيب والفلترة
# ==========================================
def live_sort_key(x):
ping_status = 1 if x.get('ping_ok', False) else 0
days = x.get('days', 0)
days_val = float('inf') if str(days).lower() in ['unlimited', 'direct', 'mac', 'none', 'null', '-'] else int(days) if str(days).isdigit() else -1
return (ping_status, days_val)
def is_arabic_sport(name, cat):
n, c = name.lower(), cat.lower()
bad = ['movie', 'series', 'drama', 'cinema', 'kids', 'news', 'documentary', 'وثائقي', 'براعم', 'جيم', 'فتافيت', 'rotana', 'osn', 'art']
foreign = ['|fr|', '|uk|', '|us|', '|de|', '|it|', '|tr|', '|es|', '|pt|', '|nl|', '|pl|', '|ru|', ' sky ', ' tnt ', 'espn', 'fox ', 'eleven', 'bt sport', 'optus', 'astro', 'super sport', 'nfl', 'nba', 'premier sports', 'digi sport']
if any(x in n or x in c for x in bad + foreign):
return False
allowed = ['bein', 'ssc', 'mbc', 'alwan', 'fajr', 'ontime', 'on time', 'on sport', 'thamania', 'ad sport', 'abu dhabi', 'ابو ظبي', 'kass', 'الكاس', 'ksa', 'dubai', 'دبي', 'رياضة', 'رياضيه', 'sport ar']
return any(x in n or x in c for x in allowed) or ' ar ' in f" {n} "
def categorize_vip_sport(name, sn):
nl = name.lower()
if 'alwan' in nl: bg = "Alwan"
elif 'bein' in nl: bg = "Bein sport"
elif 'on time' in nl or 'ontime' in nl or 'on sport' in nl: bg = "On sport"
elif 'fajr' in nl: bg = "Fajr"
elif 'mbc masr 2' in nl or 'مصر 2' in nl: bg = "Mbc masr 2"
elif 'mbc action' in nl or 'action' in nl or 'أكشن' in nl: bg = "Mbc action"
elif 'mbc' in nl and ('masr' in nl or 'مصر' in nl or '1' in nl): bg = "Mbc masr 1"
elif 'kass' in nl or 'الكاس' in nl or 'كأس' in nl: bg = "Alkass"
elif 'ad sport' in nl or 'abu dhabi' in nl or 'ابو ظبي' in nl or 'أبوظبي' in nl: bg = "Abu Dhabi"
elif 'dubai' in nl or 'دبي' in nl: bg = "Dubai Sport"
elif 'ssc' in nl: bg = "SSC"
else: bg = "Other sport"
q_str = "[4K]" if any(k in nl for k in ['4k', 'uhd']) else "[FHD]" if any(k in nl for k in ['fhd', '1080']) else "[HD]" if any(k in nl for k in ['hd', '720']) else "[SD]"
return f"{bg} [{sn}] {q_str}"
def normalize_search_text(text):
t = str(text).lower()
t = re.sub(r'\b(بين|بي ان|be in)\b', 'bein', t)
t = re.sub(r'\b(sports|sport|سبورت|رياضة|رياضه)\b', 'sport', t)
t = re.sub(r'[^a-z0-9\sأ-ي]', '', t)
return t
def smart_search_match(query, line):
q_norm = normalize_search_text(query)
line_norm = normalize_search_text(line)
for word in q_norm.split():
if word not in line_norm: return False
return True
def get_vip_sort_key(ch):
name, group = ch.get('name', '').lower(), ch.get('group', '').lower()
server_name = re.search(r'\[(.*?)\]', ch.get('name', '')).group(1).lower() if re.search(r'\[(.*?)\]', ch.get('name', '')) else ""
cat_prio = 1 if 'alwan' in group else 2 if 'bein sport' in group else 3 if 'on sport' in group else 4 if 'fajr' in group else 5 if 'mbc masr 1' in group else 6 if 'mbc masr 2' in group else 7 if 'mbc action' in group else 8 if 'alkass' in group else 9 if 'abu dhabi' in group else 10 if 'dubai sport' in group else 11 if 'ssc' in group else 12
q_prio = 4 if '[4k]' in group else 3 if '[fhd]' in group else 2 if '[hd]' in group else 1 if '[sd]' in group else 1
sub_prio = 2 if any(x in name for x in ['max', 'afc', 'extra', 'xtra']) else 1
ch_num = int(re.search(r'\b(\d+)\b', name).group(1)) if re.search(r'\b(\d+)\b', name) else 999
base_name = re.sub(r'\[.*?\]|\(.*?\)|\|.*?\||\{.*?\}', '', name)
for q in ['4k', 'uhd', 'fhd', '1080p', '1080i', '1080', 'hd', '720p', '720', 'sd', '576p', '480p', 'low', 'mob', 'hevc', 'h265', 'vip', 'premium', 'ar', 'arabic']:
base_name = re.sub(rf'\b{q}\b', '', base_name)
base_name = re.sub(r'[^a-z0-9]', '', base_name)
return (cat_prio, q_prio, sub_prio, ch_num, base_name, server_name)
# ==========================================
# 🛠️ معالج الروابط والاستخراج الشامل والمطور
# ==========================================
def parse_any_link(url):
try:
u_m = re.search(r'username=([^&]+)', url, re.IGNORECASE)
p_m = re.search(r'password=([^&]+)', url, re.IGNORECASE)
if u_m and p_m:
host_match = re.search(r'(https?://[^/]+)', url, re.IGNORECASE)
if host_match:
return host_match.group(1), u_m.group(1), p_m.group(1)
m_path = re.search(r'(https?://[^/]+)/(?:live|movie|series|play)/([^/]+)/([^/]+)/', url, re.IGNORECASE)
if m_path:
return m_path.group(1), m_path.group(2), m_path.group(3)
parsed = urlparse(url.strip())
path_parts = [p for p in parsed.path.split('/') if p]
ignore_starts = ['download', 'portal', 'stalker_portal', 'c', 'get.php', 'live.php', 'player_api.php', 'enigma2', 'mag']
if len(path_parts) >= 3 and path_parts[0].lower() not in ignore_starts:
if not '.' in path_parts[0] and not '.' in path_parts[1]:
return f"{parsed.scheme}://{parsed.netloc}", path_parts[0], path_parts[1]
except: pass
return None, None, None
def clean_camouflage(text):
return re.sub(r'[@#]+', '', text)
def extract_all_links_from_text(text):
accounts = []
seen = set()
clean_text = re.sub(r'(https?)\s*:\s*//\s*([a-zA-Z0-9.-]+)\s*\.\s*([a-zA-Z]+)', r'\1://\2.\3', text)
lines = [line.strip() for line in clean_text.split('\n') if line.strip()]
# 1. التقطيع الذكي: التعامل مع السطور المتتالية (Host, User, Pass)
for i in range(len(lines) - 2):
h_m = re.search(r'(https?://[a-zA-Z0-9.-]+(?::\d+)?)', lines[i])
if not h_m or 'username=' in lines[i].lower(): continue
u_line, p_line = lines[i+1], lines[i+2]
u_m = re.search(r'(?:user(?:name)?|👤user)\s*[=:]?\s*([a-zA-Z0-9_.\-@#]+)', u_line, re.IGNORECASE)
if not u_m: u_m = re.search(r'^([a-zA-Z0-9_.\-@#]+)$', u_line)
p_m = re.search(r'(?:pass(?:word)?|🔐password)\s*[=:]?\s*([a-zA-Z0-9_.\-@#]+)', p_line, re.IGNORECASE)
if not p_m: p_m = re.search(r'^([a-zA-Z0-9_.\-@#]+)$', p_line)
if h_m and u_m and p_m and 'http' not in u_line and 'http' not in p_line:
h = h_m.group(1).rstrip('/')
u = clean_camouflage(u_m.group(1))
p = clean_camouflage(p_m.group(1))
uid_str = f"x_{h}_{u}"
if uid_str not in seen:
accounts.append({'type': 'xtream', 'host': h, 'user': u, 'pass': p, '_uid': str(uuid.uuid4())[:8]})
seen.add(uid_str)
# 2. الاستخراج المدمج في الروابط
for link in re.findall(r'(https?://[^\s<>"\'\|]+)', clean_text, re.IGNORECASE):
link = link.rstrip('.')
h, u, p = parse_any_link(link)
if h and u and p:
u, p = clean_camouflage(u), clean_camouflage(p)
uid_str = f"x_{h}_{u}"
if uid_str not in seen:
accounts.append({'type': 'xtream', 'host': h, 'user': u, 'pass': p, '_uid': str(uuid.uuid4())[:8]})
seen.add(uid_str); seen.add(link)
# 3. استخراج البلوكات المتباعدة
for b in re.split(r'\n\s*\n', clean_text):
h_m = re.search(r'(https?://[a-zA-Z0-9.-]+(?::\d+)?)', b)
u_m = re.search(r'(?:User|Username|👤User)[^\w\n]*([a-zA-Z0-9_.\-@#]+)', b, re.IGNORECASE)
p_m = re.search(r'(?:Pass|Password|🔐Password)[^\w\n]*([a-zA-Z0-9_.\-@#]+)', b, re.IGNORECASE)
if h_m and u_m and p_m:
h, u, p = h_m.group(1).rstrip('/'), clean_camouflage(u_m.group(1)), clean_camouflage(p_m.group(1))
uid_str = f"x_{h}_{u}"
if uid_str not in seen:
accounts.append({'type': 'xtream', 'host': h, 'user': u, 'pass': p, '_uid': str(uuid.uuid4())[:8]})
seen.add(uid_str)
# 4. الماكات
current_mac_host = None
mac_patt = re.compile(r'([0-9A-Fa-f]{2}[:\-][0-9A-Fa-f]{2}[:\-][0-9A-Fa-f]{2}[:\-][0-9A-Fa-f]{2}[:\-][0-9A-Fa-f]{2}[:\-][0-9A-Fa-f]{2})', re.IGNORECASE)
url_patt = re.compile(r'(https?://[^\s]+)')
for line in lines:
u_m = url_patt.search(line.replace(" ", ""))
if u_m and 'username=' not in line:
current_mac_host = u_m.group(1).rstrip('/')
m_m = mac_patt.search(line)
if m_m and current_mac_host:
m = m_m.group(1).upper().replace('-', ':')
uid_str = f"m_{current_mac_host}_{m}"
if uid_str not in seen:
accounts.append({'type': 'mac', 'host': current_mac_host, 'mac': m, '_uid': str(uuid.uuid4())[:8]})
seen.add(uid_str)
seen.add(current_mac_host+m)
# 5. الروابط المباشرة المتبقية
direct_links = re.findall(r'(https?://[^\s<>"\'\|]+)', clean_text, re.IGNORECASE)
for link in direct_links:
link = link.rstrip('.')
if link not in seen and not parse_any_link(link)[0]:
if any(x in link.lower() for x in ['.m3u8', '.ts', '.m3u', 'mpegts', '/stream/', 'amazonaws', '.dev', '.top', 'ok.ru', 'token=', 'index.', '/play/', 'get.php?']):
uid_str = f"d_{link}"
if uid_str not in seen:
accounts.append({'type': 'direct', 'url': link, '_uid': str(uuid.uuid4())[:8]})
seen.add(uid_str)
seen.add(link)
return accounts
# ==========================================
# 📡 محرك الماك المطوّر
# ==========================================
def smart_mac_session(mac, change_ua=False):
session = requests.Session()
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36" if change_ua else "Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG322 stbapp/5.0.1"
session.headers.update({"User-Agent": ua, "Cookie": f"mac={mac}; stb_lang=en; timezone=Europe/London;"})
retry_strategy = Retry(total=1, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["HEAD", "GET", "OPTIONS"], backoff_factor=0.5)
session.mount("http://", HTTPAdapter(max_retries=retry_strategy))
session.mount("https://", HTTPAdapter(max_retries=retry_strategy))
return session
def clean_mac_address(mac):
return mac.replace(':', '')
def test_mac_portal(host, mac):
session = smart_mac_session(mac)
conns, is_working, exp = "0 / 1", False, "Unlimited"
try:
r_hs = session.get(f"{host}/portal.php?type=stb&action=handshake&mac={mac}", timeout=5, verify=False).json()
if r_hs.get('js') and isinstance(r_hs['js'], dict) and 'token' in r_hs['js']:
session.headers.update({"Authorization": f"Bearer {r_hs['js']['token']}"})
prof_req = session.get(f"{host}/portal.php?type=stb&action=get_profile&mac={mac}", timeout=5, verify=False).json()
if 'js' in prof_req and prof_req['js'] == []:
return False, "Unlimited", "N/A"
prof = prof_req.get('js', {})
if prof and isinstance(prof, dict):
is_working = True
conns = f"{prof.get('active_cons', '0')} / {prof.get('max_connections', '1')}"
for key in ['phone', 'expire_billing_date', 'expiry', 'expire', 'end_date', 'expiration', 'expiration_date', 'account_balance']:
if key in prof and prof[key] and str(prof[key]) not in ["0", "0000-00-00 00:00:00", "0000-00-00", "null", "none"]:
exp = parse_timestamp(str(prof[key]))
break
except: pass
if not is_working: return False, "Unlimited", "N/A"
if exp == "Unlimited":
mac_clean = clean_mac_address(mac)
endpoints = [
f"/portal.php?action=get_main_info&type=account_info&mac={mac}&JsHttpRequest=1-xml",
f"/portal.php?action=get_agreement_info&type=account_info&mac={mac}&JsHttpRequest=1-xml",
f"/portal.php?action=get_payment_info&type=account_info&mac={mac}&JsHttpRequest=1-xml",
f"/stalker_portal/server/api.php?action=get_subscription_info&mac={mac_clean}",
f"/stalker_portal/api/v2/player/get_subscription_info?mac={mac_clean}",
f"/server/api.php?action=get_subscription_info&mac={mac_clean}"
]
for ep in endpoints:
try:
resp = session.get(f"{host.rstrip('/')}{ep}", timeout=5, verify=False)
if resp.status_code == 200:
raw = resp.text.strip()
json_match = re.search(r'({.*})', raw, re.DOTALL)
if json_match: raw = json_match.group(1)
try:
data = json.loads(raw)
if 'js' in data and data['js'] == []: continue
js_data = data.get('js', data) if isinstance(data, dict) else data
if isinstance(js_data, dict):
for key in ['phone', 'expire_billing_date', 'expiry', 'expire', 'end_date', 'expiration', 'expiration_date']:
if key in js_data and js_data[key] and str(js_data[key]) not in ["0", "0000-00-00 00:00:00", "0000-00-00", "null", "none"]:
return True, parse_timestamp(str(js_data[key])), conns
except: pass
date_match = re.search(r'(\d{4}-\d{2}-\d{2})|([A-Z][a-z]+ \d{1,2}, \d{4})', raw)
if date_match: return True, parse_timestamp(date_match.group(0)), conns
except: continue
return True, exp, conns
def extract_mac_to_m3u_logic(portal, mac, mode="🍿 الكل (لايف+أفلام+مسلسلات)", add_ua=False, ext_choice=".m3u8"):
session = smart_mac_session(mac)
try:
hs = session.get(f"{portal}/portal.php?type=stb&action=handshake&mac={mac}", timeout=5, verify=False).json()
if hs.get('js') and 'token' in hs['js']: session.headers.update({"Authorization": f"Bearer {hs['js']['token']}"})
except: pass
try: genres = session.get(f"{portal}/portal.php?type=itv&action=get_genres&mac={mac}&JsHttpRequest=1-xml", timeout=20, verify=False).json().get("js", [])
except: return False, "❌ فشل الاتصال بالبورتال.", 0
if not genres: return False, "❌ لم يتم العثور على تصنيفات.", 0
m3u_lines = ["#EXTM3U", "# Generated by Shalaby VIP Panel\n"]
total_channels = 0
ua_suffix = "|User-Agent=IPTVSmartersPro" if add_ua else ""
ext_val = "m3u8" if ext_choice == ".m3u8" else "ts" if ext_choice == ".ts" else ""
strategy = "create_link"
first_genre_id = genres[0].get('id') if genres else None
if first_genre_id:
try:
test_data = session.get(f"{portal}/portal.php?type=itv&action=get_ordered_list&genre={first_genre_id}&p=1&mac={mac}&JsHttpRequest=1-xml", timeout=5, verify=False).json().get("js", {}).get("data", [])
if test_data:
test_cmd = test_data[0].get("cmd", "") or test_data[0].get("id")
test_link = session.get(f"{portal}/portal.php?type=itv&action=create_link&cmd={urllib.parse.quote(str(test_cmd))}&mac={mac}", timeout=5, verify=False).json()
if test_link.get("js") and test_link["js"].get("id") is None:
session = smart_mac_session(mac, change_ua=True)
test_link_2 = session.get(f"{portal}/portal.php?type=itv&action=create_link&cmd={urllib.parse.quote(str(test_cmd))}&mac={mac}", timeout=5, verify=False).json()
if test_link_2.get("js") and test_link_2["js"].get("id") is None: strategy = "raw_fallback"
except: strategy = "raw_fallback"
if mode in ["⚽ رياضة فقط VIP", "📺 لايف فقط", "🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"]:
domain_parts = portal.split('//')[-1].split(':')[0].split('.')
valid_parts = [p for p in domain_parts if p.lower() not in ['com', 'net', 'org', 'info', 'tv'] and len(p)>2]
sn = re.sub(r'[^a-zA-Z0-9]', '', valid_parts[0].capitalize() if valid_parts else "MAC")
sports_channels = []
def grab_genre(g):
res_lines, res_sports = [], []
g_id, g_name = g.get("id"), g.get("title", "Unknown")
p = 1
seen = set()
while p <= 15:
try:
data = session.get(f"{portal}/portal.php?type=itv&action=get_ordered_list&genre={g_id}&p={p}&mac={mac}&JsHttpRequest=1-xml", timeout=12, verify=False).json().get("js", {}).get("data", [])
if not data or not isinstance(data, list): break
added = False
for ch in data:
name, cid, cmd_str = ch.get("name", "Unknown"), ch.get("id"), str(ch.get("cmd", ""))
if not cid or cid in seen: continue
seen.add(cid); added = True
if strategy == "create_link":
stream_url = f"{portal}/portal.php?type=itv&action=create_link&cmd={urllib.parse.quote(cmd_str)}&mac={mac}"
else:
stream_url = f"{portal}/play/live.php?mac={mac}&stream={cid}"
if ext_val: stream_url += f"&extension={ext_val}"
logo = ch.get("logo", "")
if mode == "⚽ رياضة فقط VIP":
if is_arabic_sport(name, g_name): res_sports.append({"name": f"{name} [{sn}]", "group": categorize_vip_sport(name, sn), "logo": get_vip_logo(name) or logo, "url": stream_url})
else: res_lines.append(f'#EXTINF:-1 tvg-logo="{logo}" group-title="{g_name}",{name}\n{stream_url}{ua_suffix}')
if not added: break
p += 1
except: break
return res_lines, res_sports
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(grab_genre, genres))
for res_l, res_s in results:
if mode == "⚽ رياضة فقط VIP": sports_channels.extend(res_s)
else: m3u_lines.extend(res_l); total_channels += len(res_l)
if mode == "⚽ رياضة فقط VIP" and sports_channels:
sports_channels.sort(key=get_vip_sort_key)
for ch in sports_channels: m3u_lines.append(f'#EXTINF:-1 tvg-logo="{ch["logo"]}" group-title="{ch["group"]}",{ch["name"]}\n{ch["url"]}{ua_suffix}'); total_channels += 1
if mode in ["🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"]:
try:
vod_cats = session.get(f"{portal}/portal.php?type=vod&action=get_categories&mac={mac}&JsHttpRequest=1-xml", timeout=10, verify=False).json().get("js", [])
for c in vod_cats:
c_id, c_name = c.get("id"), c.get("title", "Movies")
p = 1
while p <= 15:
try:
vod_data = session.get(f"{portal}/portal.php?type=vod&action=get_ordered_list&category={c_id}&p={p}&mac={mac}&JsHttpRequest=1-xml", timeout=10, verify=False).json().get("js", {}).get("data", [])
if not vod_data: break
for vod in vod_data:
stream_url = f"{portal}/play/vod.php?mac={mac}&stream={vod.get('cmd', '') or vod.get('id')}&extension=mp4"
m3u_lines.append(f'#EXTINF:-1 tvg-logo="{vod.get("screenshot_uri", "")}" group-title="Movies - {c_name}",{vod.get("name", "Unknown Movie")}\n{stream_url}{ua_suffix}'); total_channels += 1
p += 1
except: break
except: pass
if mode == "🍿 الكل (لايف+أفلام+مسلسلات)":
try:
ser_cats = session.get(f"{portal}/portal.php?type=series&action=get_categories&mac={mac}&JsHttpRequest=1-xml", timeout=10, verify=False).json().get("js", [])
for c in ser_cats:
c_id, c_name = c.get("id"), c.get("title", "Series")
p = 1
while p <= 15:
try:
ser_data = session.get(f"{portal}/portal.php?type=series&action=get_ordered_list&category={c_id}&p={p}&mac={mac}&JsHttpRequest=1-xml", timeout=10, verify=False).json().get("js", {}).get("data", [])
if not ser_data: break
for ser in ser_data:
stream_url = f"{portal}/play/series.php?mac={mac}&stream={ser.get('id')}&extension=mp4"
m3u_lines.append(f'#EXTINF:-1 tvg-logo="{ser.get("screenshot_uri", "")}" group-title="Series - {c_name}",{ser.get("name", "Unknown Series")}\n{stream_url}{ua_suffix}'); total_channels += 1
p += 1
except: break
except: pass
if total_channels == 0: return False, "❌ لم يتم العثور على قنوات.", 0
return True, "\n".join(m3u_lines), total_channels
# ==========================================
# 🔥 دالة السحب المباشر الشغالة الأصلية لـ player_api (النسخة الصاروخية الناجحة) 🔥
# ==========================================
def custom_api_extractor_sync(host, user, pw, mode, ext_choice, add_ua):
output_file = f"Extracted_VIP_{user}_API.m3u"
extracted_count = 0
live_ext = "" if ext_choice == "بدون صيغة" else ext_choice
ua_suffix = "|User-Agent=IPTVSmartersPro" if add_ua else ""
host = host.rstrip('/')
sn = host.split('//')[-1].split(':')[0].split('.')[0].capitalize()
# 1. سحب أسماء الأقسام أولاً كـ الخريطة المحلية (لعدم إجهاد السيرفر)
cat_map = {}
try:
cats_res = requests.get(f"{host}/player_api.php?username={user}&password={pw}&action=get_live_categories", headers={'User-Agent': 'IPTVSmartersPro'}, timeout=15, verify=False)
if cats_res.status_code == 200:
cats_data = cats_res.json()
if isinstance(cats_data, list):
for c in cats_data:
cat_map[str(c.get('category_id'))] = str(c.get('category_name', 'Live TV'))
except: pass
# 2. سحب جميع قنوات اللايف بضغطة واحدة (أسرع مليون مرة وتتجنب الـ 502 والـ Timeout تماماً)
vip_ch = []
try:
streams_res = requests.get(f"{host}/player_api.php?username={user}&password={pw}&action=get_live_streams", headers={'User-Agent': 'IPTVSmartersPro'}, timeout=35, verify=False)
if streams_res.status_code == 200:
raw_text = streams_res.text.strip()
if raw_text.startswith('\ufeff'): raw_text = raw_text[1:]
streams_data = json.loads(raw_text)
if isinstance(streams_data, list):
with open(output_file, 'w', encoding='utf-8') as f:
f.write("#EXTM3U\n")
for s in streams_data:
name = str(s.get('name', ''))
cat_id = str(s.get('category_id', ''))
cat_name = cat_map.get(cat_id, 'Live TV')
logo = s.get("stream_icon", "")
stream_id = s.get('stream_id')
if not stream_id: continue
url = f"{host}/live/{user}/{pw}/{stream_id}{live_ext}"
if mode == "⚽ رياضة فقط VIP":
if is_arabic_sport(name, cat_name):
chic_name = f"{name} [{sn}]"
group = categorize_vip_sport(name, sn)
vip_ch.append({"name": chic_name, "group": group, "logo": get_vip_logo(name) or logo, "url": url})
else: # 📺 لايف فقط (كل قنوات اللايف)
f.write(f'#EXTINF:-1 tvg-logo="{logo}" group-title="{cat_name}",{name}\n{url}{ua_suffix}\n')
extracted_count += 1
# كتابة باقات الرياضة المرتبة لو تم اختيارها
if mode == "⚽ رياضة فقط VIP" and vip_ch:
vip_ch.sort(key=get_vip_sort_key)
for ch in vip_ch:
f.write(f'#EXTINF:-1 tvg-logo="{ch["logo"]}" group-title="{ch["group"]}",{ch["name"]}\n{ch["url"]}{ua_suffix}\n')
extracted_count += 1
except: pass
return extracted_count > 0, output_file, extracted_count
def custom_get_php_extractor(host, user, pw, mode, ext_choice, add_ua):
live_ext = "m3u8" if ext_choice == ".m3u8" else "ts"
ua_suffix = "|User-Agent=IPTVSmartersPro" if add_ua else ""
url = f"{host.rstrip('/')}/get.php?username={user}&password={pw}&type=m3u_plus&output={live_ext}"
output_file = f"Extracted_VIP_{user}_GET.m3u"
extracted_count = 0
try:
r = requests.get(url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}, timeout=35, verify=False)
if r.status_code != 200 or '#EXTM3U' not in r.text[:200]:
return False, "❌ فشل سحب الملف عبر get.php", 0
raw_lines = r.text.splitlines()
with open(output_file, 'w', encoding='utf-8') as f:
f.write("#EXTM3U\n")
sn = host.split('//')[-1].split(':')[0].split('.')[0].capitalize()
i = 0
while i < len(raw_lines):
line = raw_lines[i]
if line.startswith("#EXTINF"):
next_line = raw_lines[i+1] if i+1 < len(raw_lines) else ""
name_match = re.search(r',(.*)$', line)
name = name_match.group(1).strip() if name_match else "Unknown"
group_match = re.search(r'group-title="([^"]+)"', line)
group = group_match.group(1) if group_match else ""
logo_match = re.search(r'tvg-logo="([^"]+)"', line)
logo = logo_match.group(1) if logo_match else ""
is_vod = '/movie/' in next_line.lower() or '/series/' in next_line.lower() or next_line.lower().endswith('.mp4') or next_line.lower().endswith('.mkv')
is_live = not is_vod
add_this = False
final_line = line
final_next = next_line + ua_suffix
if mode == "⚽ رياضة فقط VIP":
if is_live and is_arabic_sport(name, group):
chic_name = f"{name} [{sn}]"
final_group = categorize_vip_sport(name, sn)
final_logo = get_vip_logo(name) or logo
final_line = re.sub(r'group-title="[^"]*"', f'group-title="{final_group}"', line) if 'group-title=' in line else f'{line} group-title="{final_group}"'
final_line = re.sub(r'tvg-logo="[^"]*"', f'tvg-logo="{final_logo}"', final_line) if 'tvg-logo=' in line else final_line
final_line = re.sub(r',(.*)$', f',{chic_name}', final_line)
add_this = True
elif mode == "📺 لايف فقط (كل القنوات)":
if is_live: add_this = True
if add_this:
f.write(f"{final_line}\n{final_next}\n")
extracted_count += 1
i += 2
else: i += 1
return extracted_count > 0, output_file, extracted_count
except Exception as e:
return False, f"❌ السيرفر لا يدعم get.php: {str(e)}", 0
# ==========================================
# ✅ الفحص الذكي ونظام المراوغة المتعدد (مع دعم الـ get_user_info والـ Strict Timeout)
# ==========================================
def check_server_smart(host, user, pwd):
agents = [
'IPTVSmartersPro',
'ExoPlayer/2.18.1 (Linux; Android 11) ExoPlayerLib/2.18.1',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
]
for agent in agents:
headers = {'User-Agent': agent, 'Accept': '*/*', 'Connection': 'keep-alive'}
try:
r = requests.get(f"{host}/player_api.php?username={user}&password={pwd}", headers=headers, timeout=(4,8), verify=False, allow_redirects=True)
if r.status_code in [200, 301, 302]:
raw = r.text.strip()
if raw.startswith('\ufeff'): raw = raw[1:]
data = json.loads(raw)
info = data.get('user_info', {})
status = str(info.get('status', '')).lower()
if not info.get('exp_date') or status not in ['active', 'trial', '1']:
try:
r2 = requests.get(f"{host}/player_api.php?username={user}&password={pwd}&action=get_user_info", headers=headers, timeout=(4,8), verify=False)
raw2 = r2.text.strip()
if raw2.startswith('\ufeff'): raw2 = raw2[1:]
data2 = json.loads(raw2)
if data2.get('user_info'):
info.update(data2.get('user_info'))
status = str(info.get('status', '')).lower()
except: pass
if status in ['active', 'trial', '1'] or str(info.get('auth', '')) == '1' or data.get('user_info'):
info['source'] = 'Xtream API'
info['conns'] = f"{info.get('active_cons', '0')} / {info.get('max_connections', '1')}"
info['formats'] = ', '.join(info.get('allowed_output_formats', [])) if isinstance(info.get('allowed_output_formats', []), list) and len(info.get('allowed_output_formats', [])) > 0 else 'HLS (m3u8), TS'
return info
except: pass
for agent in agents:
headers = {'User-Agent': agent, 'Accept': '*/*'}
try:
r = requests.get(f"{host}/get.php?username={user}&password={pwd}&type=m3u_plus", headers=headers, timeout=(4,8), verify=False)
if r.status_code == 200 and '#EXTM3U' in r.text[:200]:
return {'status': 'Active', 'exp_date': None, 'created_at': None, 'ch_count': r.text.count('#EXTINF'), 'source': 'get.php (M3U)', 'formats': 'm3u8, ts', 'conns': 'N/A'}
except: pass
return None
def test_live_stream_ping(host, user, pw):
agents = ['IPTVSmartersPro', 'ExoPlayer/2.18.1']
for agent in agents:
headers = {'User-Agent': agent, 'Accept': '*/*'}
try:
api_url = f"{host.rstrip('/')}/player_api.php?username={user}&password={pw}&action=get_live_streams"
r = requests.get(api_url, headers=headers, timeout=(4,8), verify=False)
if r.status_code == 200:
data = r.json()
if isinstance(data, list) and len(data) > 0:
test_url = f"{host.rstrip('/')}/live/{user}/{pw}/{data[0].get('stream_id')}.ts"
res = requests.get(test_url, headers=headers, stream=True, timeout=(4,10), verify=False)
status = res.status_code
if status in [200, 302, 206]:
chunk = next(res.iter_content(chunk_size=1024), b"")
res.close()
if b"<html" in chunk.lower() or b"error" in chunk.lower()[:20]:
return False, "❌ صفحة خطأ (البث متوقف)."
return True, "✅ يعمل بقوة."
res.close()
return False, f"❌ متوقف (كود {status})."
return False, "⚠️ الحساب يعمل لكن قنوات اللايف فارغة."
except: continue
return False, "❌ فشل الاتصال."
def advanced_direct_link_test(url):
try:
res = requests.get(url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}, stream=True, timeout=(4,8), verify=False, allow_redirects=True)
if res.status_code in [200, 206, 302, 304]:
chunk = next(res.iter_content(chunk_size=2048), b"")
res.close()
text_chunk = chunk.decode('utf-8', errors='ignore').strip()
if text_chunk.startswith("#EXTM3U") or "#EXTINF" in text_chunk:
return True, True, max(1, text_chunk.count("#EXTINF"))
if "<html" in text_chunk.lower() and not any(x in url for x in ['.dev', '.workers', '.top']):
return False, False, 0
return True, False, 0
except: pass
return False, False, 0
# ==========================================
# ⚡ محرك Asyncio للتجميع المتوازي
# ==========================================
def process_single_account_sync(a):
try:
if a['type'] == 'xtream':
info = check_server_smart(a['host'], a['user'], a['pass'])
if info:
d = calculate_days_left(info.get('exp_date'))
ping_ok, _ = test_live_stream_ping(a['host'], a['user'], a['pass'])
a.update({'days': d, 'type_auth': info.get('source', 'Xtream'), 'created_at': parse_timestamp(info.get('created_at')), 'exp_date': parse_timestamp(info.get('exp_date')), 'status': 'يعمل بقوة' if ping_ok else 'متصل (البث متوقف)', 'ping_ok': ping_ok, 'formats': info.get('formats', 'HLS, TS'), 'conns': info.get('conns', 'N/A')})
return a
elif a['type'] == 'mac':
prof_ok, exp, conns = test_mac_portal(a['host'], a['mac'])
if prof_ok:
a.update({'days': calculate_days_left(exp), 'type_auth': 'Portal MAC', 'created_at': '-', 'exp_date': exp, 'status': 'يعمل بقوة', 'ping_ok': True, 'formats': 'Stalker/MAG', 'conns': conns})
else:
a.update({'days': 'MAC', 'type_auth': 'Portal MAC', 'created_at': '-', 'exp_date': '-', 'status': 'لا يعمل', 'ping_ok': False, 'formats': 'Unknown', 'conns': 'N/A'})
return a
elif a['type'] == 'direct':
ping_ok, is_playlist, ch_count = advanced_direct_link_test(a['url'])
if ping_ok:
if is_playlist:
a.update({'host': 'Playlist M3U', 'days': 'Direct', 'type_auth': 'Playlist', 'status': 'قائمة تشغيل تعمل', 'ping_ok': True, 'formats': 'm3u8/ts', 'ch_count': ch_count})
else:
a.update({'host': 'Direct Link', 'days': 'Direct', 'type_auth': 'Stream', 'status': 'بث مباشر يعمل', 'ping_ok': True, 'formats': 'ts/m3u8'})
else:
a.update({'host': 'Direct Link', 'days': 'Direct', 'type_auth': 'Stream', 'status': 'لا يعمل', 'ping_ok': False, 'formats': 'Unknown'})
return a
except: pass
return None
async def run_async_scan(accs, progress_bar, status_msg, live_placeholder, context):
loop = asyncio.get_running_loop()
valid_found = []
total = len(accs)
completed = 0
with ThreadPoolExecutor(max_workers=30) as executor:
tasks = [loop.run_in_executor(executor, process_single_account_sync, a) for a in accs]
for coro in asyncio.as_completed(tasks):
result = await coro
completed += 1
progress_bar.progress(completed / total)
status_msg.warning(f"⚡ فحص صاروخي بـ Asyncio ({completed}/{total})...")
if result:
valid_found.append(result)
valid_found.sort(key=live_sort_key, reverse=True)
with live_placeholder.container():
for srv in valid_found:
ping_icon = "🟢" if srv.get('ping_ok') else "🔴"
days_str = f"{srv.get('days', 0)} يوم" if str(srv.get('days', 0)).isdigit() else str(srv.get('days', 0))
if srv.get('type') == 'direct':
st.success(f"{ping_icon} {srv['status']} | 🌐 `{srv['url']}` | ⏳ {days_str}")
elif srv.get('type') == 'mac':
st.success(f"{ping_icon} {srv['status']} | 🌐 `{srv['host']}` | 💳 `{srv['mac']}` | ⏳ {days_str}")
else:
st.success(f"{ping_icon} {srv['status']} | 🌐 `{srv['host']}` | 👤 `{srv['user']}` | ⏳ {days_str}")
try:
if result.get('type') == 'xtream': save_to_global_vips({'host': result['host'], 'user': result['user'], 'pass': result['pass'], 'days': result.get('days')})
elif result.get('type') == 'mac': save_to_global_vips({'host': result['host'], 'mac': result['mac'], 'days': result.get('days'), 'type': 'mac'})
except: pass
live_placeholder.empty()
return valid_found
def safe_async_run(coro):
try: loop = asyncio.get_running_loop()
except RuntimeError: loop = None
if loop and loop.is_running():
result = [None]
def _run():
new_loop = asyncio.new_event_loop()
asyncio.set_event_loop(new_loop)
result[0] = new_loop.run_until_complete(coro)
t = threading.Thread(target=_run)
t.start()
t.join()
return result[0]
else: return asyncio.run(coro)
def extract_sports_unified(host, user, pwd, skip_clean=False, ext=".m3u8"):
domain_parts = host.split('//')[-1].split(':')[0].split('.')
valid_parts = [p for p in domain_parts if p.lower() not in ['com', 'net', 'org', 'info', 'tv'] and len(p)>2]
sn = re.sub(r'[^a-zA-Z0-9]', '', valid_parts[0].capitalize() if valid_parts else domain_parts[0].capitalize())
try:
r = requests.get(f"{host}/player_api.php?username={user}&password={pwd}", headers=get_headers(), timeout=(4,10), verify=False).json()
info = r.get('user_info', {})
if info.get('status') != 'Active' and str(info.get('auth', '')) != '1':
return False, f"❌ متوقف: {sn}"
save_server_to_db(host, user, pwd, calculate_days_left(info.get('exp_date')), auth_type='Xtream', created_at=parse_timestamp(info.get('created_at')), exp_date=parse_timestamp(info.get('exp_date')))
# استخدام دالة التجميع السريعة المباشرة لعدم إغلاق السيرفر
cat_map = {}
try:
cats = requests.get(f"{host}/player_api.php?username={user}&password={pwd}&action=get_live_categories", timeout=15, verify=False).json()
if isinstance(cats, list):
for c in cats: cat_map[str(c.get('category_id'))] = str(c.get('category_name', 'Live TV'))
except: pass
streams = requests.get(f"{host}/player_api.php?username={user}&password={pwd}&action=get_live_streams", timeout=25, verify=False).json()
new_chans = []
if isinstance(streams, list):
for s in streams:
name = str(s.get('name', ''))
cat_name = cat_map.get(str(s.get('category_id')), 'Live TV')
if is_arabic_sport(name, cat_name):
ch_id = f"ch_{s.get('stream_id')}_{re.sub(r'\W','',host.lower())}_{re.sub(r'\W','',user.lower())}"
new_chans.append({"id": ch_id, "name": f"{name} [{sn}]", "group": categorize_vip_sport(name, sn), "logo": get_vip_logo(name) or s.get("stream_icon", ""), "url": f"{host}/live/{user}/{pwd}/{s.get('stream_id')}{ext}", "userAgent": "IPTVSmartersPro"})
if not new_chans: return False, f"⚠️ لا يوجد باقات رياضية في {sn}"
db = load_db(); merged = {c['id']: c for c in db}
for c in new_chans: merged[c['id']] = c
final = list(merged.values()); final.sort(key=get_vip_sort_key); save_db(final)
return True, f"✅ تم سحب {len(new_chans)} باقة من [{sn}]"
except: return False, f"❌ خطأ اتصال في {sn}"
# ==========================================
# 🎨 واجهة البطاقات الموحدة (Super UX)
# ==========================================
def render_server_card(server, idx, context="tab1"):
t = server.get('type')
days_val = server.get('days', 0)
days_str = f"{days_val} يوم" if str(days_val).isdigit() else str(days_val)
uid_str = server.get('_uid', str(uuid.uuid4())[:8])
base_k = f"{context}_{idx}_{uid_str}"
force_open = f"file_{base_k}" in st.session_state or f"ping_res_{base_k}" in st.session_state or f"prep_err_{base_k}" in st.session_state or f"preview_url_{base_k}" in st.session_state
ping_icon = "🟢" if server.get('ping_ok') else "🔴"
if t == 'direct':
title = f"{ping_icon} {server['status']} | 🌐 {server.get('url', '')} | ⏳ {days_str}"
elif t == 'mac':
title = f"{ping_icon} {server['status']} | 🌐 {server.get('host', '')} | 💳 {server.get('mac', '')} | 👥 {server.get('conns', '0/1')} | 🛑 {server.get('exp_date', 'Unlimited')} | ⏳ {days_str}"
else:
title = f"{ping_icon} {server['status']} | 🌐 {server.get('host', '')} | 👤 {server.get('user', '')} | 🔑 {server.get('pass', '')} | 👥 {server.get('conns', 'N/A')} | 🛑 {server.get('exp_date', 'Unlimited')} | ⏳ {days_str}"
title += "\u200B" * st.session_state.reset_exp
with st.expander(title, expanded=force_open):
if t == 'xtream':
st.markdown("### 📋 بيانات السيرفر (للنسخ السريع):")
c1, c2, c3 = st.columns(3)
c1.text_input("🌐 الهوست (Host):", value=server.get('host', ''), key=f"h_{base_k}")
c2.text_input("👤 اليوزر (User):", value=server.get('user', ''), key=f"u_{base_k}")
c3.text_input("🔑 الباسورد (Pass):", value=server.get('pass', ''), key=f"p_{base_k}")
m3u_link = f"{server['host'].rstrip('/')}/get.php?username={server['user']}&password={server['pass']}&type=m3u_plus"
st.text_input("🔗 رابط M3U المباشر:", value=m3u_link, key=f"m3u_link_{base_k}")
extinf_format = f'#EXTINF:-1,{server.get("host", "")} | {server.get("user", "")}\n{m3u_link}'
st.text_area("📄 صيغة ملف M3U الكاملة:", value=extinf_format, height=68, key=f"m3u_txt_{base_k}")
st.markdown("---")
col_ping, col_preview = st.columns(2)
with col_ping:
if st.button("📡 فحص دقة البث الفعلي (GET)", key=f"ping_{base_k}"):
with st.spinner("جاري فحص داتا الفيديو..."):
ok, msg = test_live_stream_ping(server['host'], server['user'], server['pass'])
st.session_state[f"ping_res_{base_k}"] = (ok, msg)
st.rerun()
with col_preview:
if st.button("📺 جلب رابط المعاينة الفورية", key=f"preview_btn_{base_k}"):
with st.spinner("جاري توليد مسار المشغل..."):
try:
r_streams = requests.get(f"{server['host'].rstrip('/')}/player_api.php?username={server['user']}&password={server['pass']}&action=get_live_streams", headers=get_headers(), timeout=12, verify=False).json()
if isinstance(r_streams, list) and len(r_streams) > 0:
st.session_state[f"preview_url_{base_k}"] = f"{server['host'].rstrip('/')}/live/{server['user']}/{server['pass']}/{r_streams[0].get('stream_id')}.m3u8"
else:
st.session_state[f"preview_err_{base_k}"] = "لم نجد قنوات للمعاينة."
except:
st.session_state[f"preview_err_{base_k}"] = "فشل توليد اتصال المعاينة."
st.rerun()
if f"ping_res_{base_k}" in st.session_state:
ok, msg = st.session_state[f"ping_res_{base_k}"]
if ok: st.success(msg)
else: st.error(msg)
if f"preview_err_{base_k}" in st.session_state:
st.error(st.session_state[f"preview_err_{base_k}"])
st.markdown("---")
st.markdown(f"### ⚙️ خيارات السحب المزدوج لللايف (جرب الدالة الأخرى لو الأولى فشلت):")
tab_api, tab_get = st.tabs(["🚀 سحب بـ player_api (السريع والمضمون)", "🌐 سحب بـ get.php (المدمج)"])
with tab_api:
st.info("💡 دي الدالة المباشرة الخفيفة اللي بتعدي من الحماية وتسحب قنوات اللايف بسرعة.")
col_m1, col_e1, col_u1, col_b1 = st.columns([3, 2, 2, 2])
selected_mode_api = col_m1.selectbox("🎯 المحتوى المستهدف:", ["⚽ رياضة فقط VIP", "📺 لايف فقط (كل الباقات)"], key=f"mode_api_{base_k}")
selected_ext_api = col_e1.selectbox("صيغة البث:", [".m3u8", ".ts", "بدون صيغة"], key=f"ext_api_{base_k}")
add_ua_api = col_u1.checkbox("إضافة حماية UA", value=False, key=f"ua_api_{base_k}")
if col_b1.button("📥 سحب داتا السيرفر (API)", key=f"btn_api_{base_k}"):
with st.spinner("جاري السحب بالـ API لللايف..."):
success, file, count = custom_api_extractor_sync(server['host'], server['user'], server['pass'], selected_mode_api, selected_ext_api, add_ua_api)
if success:
st.session_state[f"file_{base_k}"], st.session_state[f"count_{base_k}"], st.session_state[f"filename_{base_k}"] = open(file, 'rb').read(), count, f"VIP_{server['user']}_API.m3u"
if f"prep_err_{base_k}" in st.session_state: del st.session_state[f"prep_err_{base_k}"]
else:
st.session_state[f"prep_err_{base_k}"] = "❌ السيرفر فارغ أو محظور בـ API بـ قنوات اللايف."
st.rerun()
with tab_get:
st.info("💡 دي دالة تحميل الـ M3U بالكامل وتقطيعه. جربها لو الأولى مسحبتش معاك حاجة.")
col_m2, col_e2, col_u2, col_b2 = st.columns([3, 2, 2, 2])
selected_mode_get = col_m2.selectbox("🎯 المحتوى المستهدف:", ["⚽ رياضة فقط VIP", "📺 لايف فقط (كل الباقات)"], key=f"mode_get_{base_k}")
selected_ext_get = col_e2.selectbox("صيغة البث:", [".m3u8", ".ts"], key=f"ext_get_{base_k}")
add_ua_get = col_u2.checkbox("إضافة حماية UA", value=False, key=f"ua_get_{base_k}")
if col_b2.button("📥 سحب داتا السيرفر (GET)", key=f"btn_get_{base_k}"):
with st.spinner("جاري تحميل M3U بالكامل وفلترة اللايف..."):
success, file, count = custom_get_php_extractor(server['host'], server['user'], server['pass'], selected_mode_get, selected_ext_get, add_ua_get)
if success:
st.session_state[f"file_{base_k}"], st.session_state[f"count_{base_k}"], st.session_state[f"filename_{base_k}"] = open(file, 'rb').read(), count, f"VIP_{server['user']}_GET.m3u"
if f"prep_err_{base_k}" in st.session_state: del st.session_state[f"prep_err_{base_k}"]
else:
st.session_state[f"prep_err_{base_k}"] = "❌ السيرفر ده قافل الـ get.php، استخدم التاب الأول (API) لسحبه."
st.rerun()
if f"prep_err_{base_k}" in st.session_state:
st.error(st.session_state[f"prep_err_{base_k}"])
elif t == 'direct':
st.markdown("### 📋 بيانات الرابط (للنسخ السريع):")
st.text_input("🔗 رابط الملف التنزيلي السريع:", value=server.get('url', ''), key=f"link_{base_k}")
st.markdown("---")
st.markdown(f"### ⚙️ خيارات التشغيل المعاينة:")
col_p1, col_p2 = st.columns(2)
with col_p1:
if server.get('type_auth') == 'Playlist' and st.button("📥 سحب القنوات (M3U Playlist)", key=f"prep_{base_k}"):
with st.spinner("جاري سحب المحتوى..."):
try:
browser_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36', 'Accept': '*/*'}
txt = requests.get(server['url'], headers=browser_headers, timeout=15, verify=False).text
st.session_state[f"file_{base_k}"], st.session_state[f"count_{base_k}"], st.session_state[f"filename_{base_k}"] = txt.encode('utf-8'), txt.count("#EXTINF"), f"Playlist_{uid_str}.m3u"
st.rerun()
except: st.error("❌ فشل سحب روابط القائمة المباشرة. السيرفر يرفض الاتصال.")
with col_p2:
if st.button("📺 تشغيل معاينة القناة المباشرة", key=f"preview_{base_k}"):
st.session_state[f"preview_url_{base_k}"] = server.get('url', '')
st.rerun()
elif t == 'mac':
st.markdown("### 📋 بيانات الماك (للنسخ السريع):")
c1, c2 = st.columns(2)
c1.text_input("🌐 البورتال (Portal):", value=server.get('host', ''), key=f"h_{base_k}")
c2.text_input("💳 الماك (MAC):", value=server.get('mac', ''), key=f"m_{base_k}")
st.markdown("---")
st.markdown(f"### ⚙️ خيارات سحب الماك الذكية (لايف فقط):")
col_m, col_e, col_u, col_b = st.columns([3, 2, 2, 2])
mac_mode = col_m.selectbox("🎯 قسم المحتوى:", ["⚽ رياضة فقط VIP", "📺 لايف فقط (كل القنوات)"], key=f"mode_{base_k}")
mac_ext = col_e.selectbox("صيغة البث:", [".m3u8", ".ts", "بدون صيغة"], index=0, key=f"ext_{base_k}")
mac_ua = col_u.checkbox("حماية التخطي UA", value=False, key=f"ua_{base_k}")
if col_b.button("📥 بدء سحب الماك", key=f"prep_btn_{base_k}"):
with st.spinner("جاري الاختراق وسحب القنوات بالتوازي..."):
ok, res_text, count = extract_mac_to_m3u_logic(server['host'], server['mac'], mac_mode, mac_ua, mac_ext)
if ok:
st.session_state[f"file_{base_k}"], st.session_state[f"count_{base_k}"], st.session_state[f"filename_{base_k}"] = res_text.encode('utf-8'), count, f"MAC_{server['mac'].replace(':','')}.m3u"
if f"prep_err_{base_k}" in st.session_state: del st.session_state[f"prep_err_{base_k}"]
else:
st.session_state[f"prep_err_{base_k}"] = res_text
st.rerun()
if f"prep_err_{base_k}" in st.session_state:
st.error(st.session_state[f"prep_err_{base_k}"])
if f"preview_url_{base_k}" in st.session_state:
p_url = st.session_state[f"preview_url_{base_k}"]
hls_demo = f"https://hls-js.netlify.app/demo/?src={urllib.parse.quote(p_url)}"
components.html(f"""<link href="https://vjs.zencdn.net/7.11.4/video-js.css" rel="stylesheet" /><script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script><video id="my-video" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto" width="100%" height="250"><source src="{p_url}" type="application/x-mpegURL"></video>""", height=270)
st.markdown(f"""<div style="background-color: #2b2b2b; padding: 15px; border-radius: 8px; text-align: center; border: 1px solid #444; margin-bottom: 15px;"><a href="intent:{p_url}#Intent;action=android.intent.action.VIEW;type=video/*;end" style="text-decoration: none;"><button style="background-color: #00cc66; color: white; border: none; padding: 11px 18px; border-radius: 6px; font-weight: bold; cursor: pointer; margin: 5px;">📱 تشغيل مشغل خارجي (VLC / MX)</button></a><a href="{hls_demo}" target="_blank" style="text-decoration: none;"><button style="background-color: #ff9800; color: white; border: none; padding: 11px 18px; border-radius: 6px; font-weight: bold; cursor: pointer; margin: 5px;">🌐 مشغل ويب نتلايف خارجي</button></a></div>""", unsafe_allow_html=True)
if f"file_{base_k}" in st.session_state:
st.success(f"✅ تم السحب بنجاح! ({st.session_state[f'count_{base_k}']} عنصر)")
st.download_button("💾 تحميل ملف السحب الخارجي (.m3u)", data=st.session_state[f"file_{base_k}"], file_name=st.session_state[f"filename_{base_k}"], key=f"dl_{base_k}")
with st.expander("🔍 فرز ونسخ سريع لقنوات الملف المسحوب مباشرة", expanded=True):
raw_text = st.session_state[f"file_{base_k}"].decode('utf-8', errors='ignore')
col_s1, col_s2 = st.columns([3, 1])
search_q = col_s1.text_input("🔍 اكتب كلمة الفرز هنا (مثل bein, ssc):", key=f"sq_{base_k}")
display_text = raw_text
if col_s2.button("فلترة 🎯", key=f"sbtn_{base_k}") or search_q:
if search_q.strip():
lines, filtered = raw_text.splitlines(), ["#EXTM3U"]
for i in range(len(lines)):
if lines[i].startswith("#EXTINF") and smart_search_match(search_q, lines[i]):
filtered.append(lines[i])
if i + 1 < len(lines): filtered.append(lines[i+1])
display_text = "\n".join(filtered)
st.success(f"🎯 تم العثور على {(len(filtered)-1)//2} باقة متطابقة.")
st.text_area("محتوى الصندوق (تحديد الكل -> نسخ):", value=display_text, height=250, key=f"all_{base_k}")
st.markdown("---")
if context == "saved":
if st.button("🗑️ حذف الحساب من السجل نهائياً", key=f"del_{base_k}"):
svs = load_servers()
svs.pop(idx)
save_all_servers_to_db(svs)
st.success("تم الحذف!")
time.sleep(1)
st.rerun()
else:
if t == 'xtream' and st.button("💾 حفظ السيرفر في قاعدة بياناتي", key=f"save_{base_k}"):
save_server_to_db(server['host'], server['user'], server['pass'], server['days'], auth_type='Xtream', created_at=server.get('created_at'), exp_date=server.get('exp_date'), conns=server.get('conns'))
st.success("✅ تم حفظ السيرفر بنجاح!")
elif t == 'mac' and st.button("💾 حفظ الماك في قاعدة بياناتي", key=f"save_{base_k}"):
save_server_to_db(server['host'], server['mac'], 'MAC Auth', server['days'], auth_type='Portal MAC', created_at=server.get('created_at'), exp_date=server.get('exp_date'), mac=server['mac'], conns=server.get('conns'))
st.success("✅ تم حفظ الماك بنجاح!")
# ==========================================
# 🤖 التلغرام والواجهة الرسومية لقاعدة البيانات والتابات
# ==========================================
@bot.message_handler(commands=['start', 'menu'])
def send_menu(message):
save_admin(message.chat.id)
bot.send_message(message.chat.id, "📺 **Shalaby IPTV Tool**\nأهلاً بك يا هندسة في لوحة الإدارة الذكية للتلفزيون!", parse_mode="Markdown")
@bot.message_handler(commands=['allservers'])
def send_all_servers(message):
vips = load_global_vips()
if not vips:
bot.reply_to(message, "📭 الصندوق الأسود فارغ حالياً.")
return
file_path = "All_Hunted_Servers.txt"
with open(file_path, "w", encoding="utf-8") as f:
f.write(f"--- 📦 إجمالي السيرفرات المصادة الشغالة: {len(vips)} ---\n\n")
for s in vips:
if s.get('type') == 'mac':
f.write(f"Portal: {s.get('host')}\nMAC: {s.get('mac')}\nDays Left: {s.get('days')}\n")
else:
f.write(f"Host: {s.get('host')}\nUser: {s.get('user')}\nPass: {s.get('pass')}\nDays Left: {s.get('days')}\n")
f.write("-" * 30 + "\n")
with open(file_path, "rb") as doc:
bot.send_document(message.chat.id, doc, caption=f"🔥 تفضل يا هندسة، ملف إجمالي السيرفرات الصالحة المصادة ({len(vips)}).")
@bot.message_handler(func=lambda message: True)
def handle_all_messages(message):
accs = extract_all_links_from_text(message.text)
if accs:
msg = bot.reply_to(message, "🔍 جاري فحص وتحليل السيرفرات المرسلة من التلغرام...")
valid = []
for a in accs:
if a['type'] == 'xtream':
info = check_server_smart(a['host'], a['user'], a['pass'])
if info:
valid.append(f"✅ `{a['host']}` | 👤 `{a['user']}` | ⏳ {calculate_days_left(info.get('exp_date'))} يوم")
elif a['type'] == 'mac':
prof_ok, exp, _ = test_mac_portal(a['host'], a['mac'])
if prof_ok:
valid.append(f"✅ `{a['host']}` | 💳 `{a['mac']}` | ⏳ {calculate_days_left(exp)} يوم")
if valid:
bot.edit_message_text(f"🎉 **تم الفحص من الموتور بنجاح!**\n\n" + "\n".join(valid), chat_id=msg.chat.id, message_id=msg.message_id, parse_mode="Markdown")
else:
bot.edit_message_text("❌ عذراً لم تنجح أي روابط مرسلة.", chat_id=msg.chat.id, message_id=msg.message_id)
def run_bot_thread_init():
for t in threading.enumerate():
if t.name == "TelegramBotThread": return
def start_polling():
try:
requests.get(f"https://api.telegram.org/bot{TOKEN}/deleteWebhook?drop_pending_updates=True", timeout=5)
except: pass
time.sleep(2)
bot.infinity_polling(timeout=20, long_polling_timeout=10, skip_pending=True)
threading.Thread(target=start_polling, name="TelegramBotThread", daemon=True).start()
run_bot_thread_init()
st.set_page_config(page_title="Shalaby IPTV Tool", page_icon="📺", layout="wide")
st.markdown("""
<style>
.block-container { max-width: 100% !important; padding: 1rem !important; padding-bottom: 5rem !important; }
section[data-testid="stSidebar"] { width: 170px !important; min-width: 170px !important; }
.main { text-align: right; direction: rtl; }
h1 { font-size: 1.8rem !important; text-align: center; color: #ff4b4b;}
div[data-baseweb="tab-list"] { display: flex; flex-direction: column; gap: 8px; }
div[data-baseweb="tab"] { width: 100%; justify-content: flex-end; background-color: #2b2b2b; border-radius: 8px; padding: 10px !important; border: 1px solid #444;}
div[data-baseweb="tab"]:hover { background-color: #ff4b4b; color: white !important;}
.stButton>button { width: 100%; border-radius: 8px; font-weight: bold; background-color: #ff4b4b; color: white; margin-top: 5px;}
</style>
""", unsafe_allow_html=True)
if not st.session_state.logged_in:
st.title("🔐 بوابة الدخول التلقائية - Shalaby IPTV Tool")
tab_in, tab_up = st.tabs(["تسجيل دخول بالحساب الحالي", "إنشاء كود مستخدم جديد"])
with tab_up:
new_user = st.text_input("اختر كود حسابك الجديد:")
if st.button("🚀 إنشاء وتفعيل التلقائي"):
if new_user.strip():
users = load_users_registry()
if new_user.strip() not in users:
users.append(new_user.strip())
save_users_registry(users)
st.session_state.username = new_user.strip()
st.session_state.logged_in = True
st.query_params["user_auth"] = new_user.strip()
components.html(f"""<script>localStorage.setItem("user_auth", "{new_user.strip()}");</script>""", height=0, width=0)
time.sleep(0.5)
st.rerun()
else: st.error("❌ الكود مستخدم من قبل.")
with tab_in:
exist_user = st.text_input("أدخل كود الدخول الخاص بك:")
if st.button("🔓 مصادقة ودخول"):
if exist_user.strip() in load_users_registry():
st.session_state.username = exist_user.strip()
st.session_state.logged_in = True
st.query_params["user_auth"] = exist_user.strip()
components.html(f"""<script>localStorage.setItem("user_auth", "{exist_user.strip()}");</script>""", height=0, width=0)
time.sleep(0.5)
st.rerun()
else: st.error("❌ كود الحساب غير مسجل بالمنظومة.")
st.stop()
with st.sidebar:
st.markdown(f"### 👤 الحساب المتصل:\n`{st.session_state.username}`")
if st.button("🚪 تسجيل خروج"):
components.html("""<script>localStorage.removeItem("user_auth");</script>""", height=0, width=0)
st.query_params.clear()
st.session_state.logged_in = False
time.sleep(0.5)
st.rerun()
st.title("📺 اللوحة الأسطورية الشاملة - Shalaby VIP Tool")
st.markdown("---")
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
"🧪 محرك الفحص الصاروخي الذكي", "🗄️ سجل سيرفراتي المحفوظة", "🚀 تجميع وتحديث الماستر داتابيز", "🏭 منقي الباقات للعملاء المخصص", "🔥 فاحص ومحول الماك الفوري", "✨ ساحب القنوات المطور (Playlist Extractor)"
])
with tab1:
st.markdown("### 🔍 أدخل البيانات أو النصوص المبعثرة للفحص:")
if 'valid_found_tab1' in st.session_state and st.session_state['valid_found_tab1']:
if st.button("🔽 غلق جميع الكروت المفتوحة ومسح الذاكرة التفاعلية", key="close_t1"):
st.session_state.reset_exp += 1
for k in list(st.session_state.keys()):
if k.startswith("file_") or k.startswith("ping_res_") or k.startswith("prep_err_") or k.startswith("preview_"): del st.session_state[k]
st.rerun()
bulk_input = st.text_area("ضع الروابط أو نصوص الماكات المبعثرة هنا:", height=150)
if st.button("🚀 إطلاق موتور فحص Asyncio الشامل"):
if not bulk_input.strip(): st.warning("⚠️ يرجى تعبئة الحقل بالنصوص أولاً.")
else:
accs = extract_all_links_from_text(bulk_input)
if len(accs) == 0: st.error("❌ لم يتم العثور على أي روابط أو ماكات صالحة للبناء التقني.")
else:
p_bar, s_msg, l_placeholder = st.progress(0), st.empty(), st.empty()
v_found = safe_async_run(run_async_scan(accs, p_bar, s_msg, l_placeholder, "tab1"))
s_msg.success(f"🎉 اكتمل مسح شبكة بايثون بنجاح! تم اصطياد وتحليل {len(v_found)} سيرفر.")
st.session_state['valid_found_tab1'] = v_found
if v_found:
c_db = load_servers()
for v in v_found:
if v.get('type') == 'xtream' and not any(s.get('host') == v['host'] and s.get('user') == v['user'] for s in c_db): c_db.append(v)
save_all_servers_to_db(c_db)
if 'valid_found_tab1' in st.session_state and st.session_state['valid_found_tab1']:
st.markdown("#### ⚙️ الحسابات المصادة والجاهزة للنسخ والسحب الفوري:")
for i, acc in enumerate(st.session_state['valid_found_tab1']): render_server_card(acc, i, context="t1")
with tab2:
st.markdown("### 🗄️ السيرفرات المسجلة في داتابيز حسابك")
svs = load_servers()
if not svs: st.warning("لا توجد حسابات محفوظة في السجل الخاص بك حالياً.")
else:
if st.button("🔽 تنظيف ذاكرة الكروت المؤقتة بالسجل", key="close_t2"):
st.session_state.reset_exp += 1
for k in list(st.session_state.keys()):
if k.startswith("file_") or k.startswith("ping_res_") or k.startswith("prep_err_") or k.startswith("preview_"): del st.session_state[k]
st.rerun()
for i, s in enumerate(svs): render_server_card(s, i, context="saved")
with tab3:
st.markdown("### 🚀 موتور تحديث الماستر داتابيز الشامل وتجميع الرياضة")
if st.button("🔄 بدء التجميع والدمج المتوازي لكافة السيرفرات"):
svs = load_servers()
if not svs: st.error("❌ سجل الحسابات فارغ تماماً!")
else:
p_bar3 = st.progress(0)
for i, s in enumerate(svs):
if s.get('type') in ['xtream', 'Xtream API']: extract_sports_unified(s['host'], s['user'], s['pass'], skip_clean=True, ext=".m3u8")
p_bar3.progress((i + 1) / len(svs))
f_db = load_db()
m3u_file = export_to_m3u(f_db, "My_Saved_Servers.m3u")
st.success(f"🎉 تم دمج وفحص وإدراج {len(f_db)} باقة رياضية محدثة بالماستر داتابيز!")
with open(m3u_file, "rb") as file:
st.download_button("💾 تحميل ملف اللستة الموحدة الشغالة لقنواتك", data=file, file_name=f"Master_Shalaby_{st.session_state.username}.m3u")
with tab4:
st.markdown("### 🏭 صناعة وتخصيص باقات خاصة للعملاء (Custom Selector)")
all_ch = load_db()
if not all_ch: st.warning("⚠️ قاعدة داتابيز القنوات فارغة، يرجى التجميع أولاً من التاب السابق.")
else:
u_groups = set(ch['group'].replace('[SD]','').replace('[HD]','').replace('[FHD]','').replace('[4K]','').strip() for ch in all_ch)
sel_g = st.multiselect("📌 حدد الباقات المراد تصديرها للعميل:", sorted(list(u_groups)))
smart_ren = st.checkbox("✨ تنظيف وإعادة هيكلة مسميات الجودة تلقائياً")
if st.button("🏭 توليد ملف باقة العميل"):
if not sel_g: st.error("❌ يرجى اختيار باقة واحدة على الأقل للتصدير.")
else:
cust_ch = []
for ch in all_ch:
if ch['group'].replace('[SD]','').replace('[HD]','').replace('[FHD]','').replace('[4K]','').strip() in sel_g:
if smart_ren: ch['group'] = categorize_vip_sport(ch['name'], re.search(r'\[(.*?)\]', ch['name']).group(1) if re.search(r'\[(.*?)\]', ch['name']) else "VIP")
cust_ch.append(ch)
if cust_ch:
c_file = export_to_m3u(cust_ch, "Client.m3u")
st.success(f"🎉 تم توليد قنوات الملف المخصص بنجاح بنجاح! ({len(cust_ch)} قناة)")
with open(c_file, "rb") as file:
st.download_button("📥 تحميل ملف العميل الجاهز المخصص", data=file, file_name=f"Client_{st.session_state.username}.m3u")
else: st.error("❌ لم نجد قنوات متوافقة مع اختيارك.")
with tab5:
st.markdown("### 🔥 فاحص الماك المنفرد والمحول المباشر لملف M3U")
col_h, col_m = st.columns(2)
m_portal = col_h.text_input("🔗 رابط البورتال (Portal URL):", placeholder="http://domain.xyz:8080")
m_addr = col_m.text_input("💳 الماك أدرس (MAC Address):", placeholder="00:1A:79:XX:XX:XX")
col_b1, col_b2, col_b3, col_b4 = st.columns([3, 2, 2, 2])
standalone_mode = col_b1.selectbox("🎯 فلتر محتوى السحب المطلوب:", ["⚽ رياضة فقط VIP", "📺 لايف فقط (كل القنوات)"], key="st_mode")
standalone_ext = col_b2.selectbox("صيغة المشاهدة المستهدفة:", [".m3u8", ".ts", "بدون صيغة"], key="st_ext")
standalone_ua = col_b3.checkbox("تفعيل حماية وتخطي UA كـ MAG", value=True)
if col_b4.button("🚀 فحص وتحويل الماك"):
if not m_portal or not m_addr: st.warning("⚠️ الرجاء تعبئة بيانات البورتال والماك معاً.")
else:
p_clean = "http://" + m_portal.strip().rstrip('/') if not m_portal.strip().startswith("http") else m_portal.strip().rstrip('/')
with st.spinner("🕵️‍♂️ جاري كسر الحماية والتحقق من التواريخ وقنوات السيرفر..."):
ok, res_text, count = extract_mac_to_m3u_logic(p_clean, m_addr.strip().upper(), standalone_mode, standalone_ua, standalone_ext)
if ok:
st.success(f"🎉 نجح التحويل! تم استخراج {count} قناة بكفاءة تامة.")
st.download_button("📥 تحميل ملف الـ M3U الناتج فوراً", data=res_text, file_name=f"Converted_{m_addr.replace(':','')}.m3u")
else: st.error(res_text)
with tab6:
st.markdown("### ✨ ساحب ومحلل محتويات قوائم التشغيل المباشرة (M3U Extractor)")
if 'valid_found_tab6' in st.session_state and st.session_state['valid_found_tab6']:
if st.button("🔽 مسح ذاكرة الكروت المؤقتة للتاب", key="close_t6"):
st.session_state.reset_exp += 1
for k in list(st.session_state.keys()):
if k.startswith("file_") or k.startswith("ping_res_") or k.startswith("prep_err_") or k.startswith("preview_"): del st.session_state[k]
st.rerun()
ex_input = st.text_area("ضع نصوص قوائم التشغيل أو روابط الـ Workers هنا لفرزها:", height=150)
if st.button("⚙️ بدء الفرز والمسح الصاروخي"):
if not ex_input.strip(): st.warning("⚠️ يرجى إدخال روابط أولاً للفرز.")
else:
accs = extract_all_links_from_text(ex_input)
if not accs: st.error("❌ لم يتم العثور على أي بيانات روابط تشغيل صالحة بالفحص.")
else:
p_bar6, s_msg6, l_placeholder6 = st.progress(0), st.empty(), st.empty()
v_accs = safe_async_run(run_async_scan(accs, p_bar6, s_msg6, l_placeholder6, "t6"))
s_msg6.success(f"🎉 اكتمل المسح بنجاح! تم استخراج {len(v_accs)} كارت تشغيل جاهز.")
st.session_state['valid_found_tab6'] = v_accs
if 'valid_found_tab6' in st.session_state and st.session_state['valid_found_tab6']:
st.markdown("#### ⚙️ الكروت النهائية بعد الفحص والترتيب التلقائي:")
for i, acc in enumerate(st.session_state['valid_found_tab6']): render_server_card(acc, i, context="t6")
st.markdown("---")
with st.expander("🔬 فحص رابط قناة مفردة (اختبار الشاشة السوداء)"):
single_link = st.text_input("🔗 أدخل رابط القناة (m3u8 أو ts):", placeholder="http://host.com/live/user/pass/123.m3u8")
if st.button("⚡ فحص استجابة القناة"):
if single_link.strip():
with st.spinner("جاري فحص القناة..."):
ping_ok, _, _ = advanced_direct_link_test(single_link.strip())
if ping_ok: st.success("✅ القناة تعمل بكفاءة (تسحب بيانات).")
else: st.error("❌ القناة لا تستجيب (قد تكون شاشة سوداء أو محمية بشدة).")
else: st.warning("الرجاء إدخال الرابط أولاً.")