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("""""", 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"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"""""", height=270) st.markdown(f"""
""", 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(""" """, 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"""""", 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"""""", 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("""""", 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("الرجاء إدخال الرابط أولاً.")