Spaces:
Running
Running
Update src/streamlit_app.py
#2
by ipnshalaby5 - opened
- src/streamlit_app.py +289 -515
src/streamlit_app.py
CHANGED
|
@@ -95,7 +95,9 @@ def save_users_registry(users_list):
|
|
| 95 |
with open(registry_file, 'w', encoding='utf-8') as f: json.dump(users_list, f)
|
| 96 |
sync_file_to_cloud(registry_file)
|
| 97 |
|
| 98 |
-
#
|
|
|
|
|
|
|
| 99 |
def parse_timestamp(ts):
|
| 100 |
if not ts or str(ts).strip() == "": return "Unlimited"
|
| 101 |
ts_str = str(ts).strip()
|
|
@@ -130,12 +132,6 @@ def calculate_days_left(exp_date_str_or_ts):
|
|
| 130 |
def save_admin(chat_id):
|
| 131 |
with open(ADMIN_FILE, "w") as f: f.write(str(chat_id))
|
| 132 |
|
| 133 |
-
def get_admin():
|
| 134 |
-
try:
|
| 135 |
-
if os.path.exists(ADMIN_FILE): return int(open(ADMIN_FILE, "r").read().strip())
|
| 136 |
-
except: pass
|
| 137 |
-
return None
|
| 138 |
-
|
| 139 |
def load_servers():
|
| 140 |
file_name = get_user_servers_file()
|
| 141 |
sync_file_from_cloud(file_name)
|
|
@@ -218,13 +214,6 @@ def normalize_search_text(text):
|
|
| 218 |
t = str(text).lower()
|
| 219 |
t = re.sub(r'\b(بين|بي ان|be in)\b', 'bein', t)
|
| 220 |
t = re.sub(r'\b(sports|sport|سبورت|رياضة|رياضه)\b', 'sport', t)
|
| 221 |
-
t = re.sub(r'\b(ام بي سي)\b', 'mbc', t)
|
| 222 |
-
t = re.sub(r'\b(اس اس سي)\b', 'ssc', t)
|
| 223 |
-
t = re.sub(r'\b(الفجر|فجر)\b', 'fajr', t)
|
| 224 |
-
t = re.sub(r'\b(اون تايم|on time|on sport|اون سبورت|اون)\b', 'ontime', t)
|
| 225 |
-
t = re.sub(r'\b(ابو ظبي|أبوظبي|ابوظبي)\b', 'abu dhabi', t)
|
| 226 |
-
t = re.sub(r'\b(الكاس|كاس|كأس|kass)\b', 'alkass', t)
|
| 227 |
-
t = re.sub(r'\b(دبي)\b', 'dubai', t)
|
| 228 |
t = re.sub(r'[^a-z0-9\sأ-ي]', '', t)
|
| 229 |
return t
|
| 230 |
|
|
@@ -264,21 +253,15 @@ def parse_any_link(url):
|
|
| 264 |
rest = re.split(r'/(?:live|movie|series)/', url, 1)[1]
|
| 265 |
rest_clean = re.sub(r'/+', '/', rest).strip('/')
|
| 266 |
parts = rest_clean.split('/')
|
| 267 |
-
if len(parts) >= 2:
|
| 268 |
-
return host, parts[0], parts[1]
|
| 269 |
except: pass
|
| 270 |
return None, None, None
|
| 271 |
|
| 272 |
-
def clean_camouflage(text):
|
| 273 |
-
return re.sub(r'[@#]+', '', text)
|
| 274 |
|
| 275 |
-
# ==========================================
|
| 276 |
-
# 🚀 الساحب الذكي الشامل (ربط 1 URL لعدة MACs)
|
| 277 |
-
# ==========================================
|
| 278 |
def extract_all_links_from_text(text):
|
| 279 |
accounts = []
|
| 280 |
seen = set()
|
| 281 |
-
|
| 282 |
clean_text = re.sub(r'(https?)\s*:\s*//\s*([a-zA-Z0-9.-]+)\s*\.\s*([a-zA-Z]+)', r'\1://\2.\3', text)
|
| 283 |
|
| 284 |
for link in re.findall(r'(https?://[^\s<>"\'\|]+)', clean_text, re.IGNORECASE):
|
|
@@ -299,11 +282,9 @@ def extract_all_links_from_text(text):
|
|
| 299 |
if h_m and u_m and p_m:
|
| 300 |
h, u, p = h_m.group(1).rstrip('/'), clean_camouflage(u_m.group(1)), clean_camouflage(p_m.group(1))
|
| 301 |
uid_str = f"x_{h}_{u}"
|
| 302 |
-
if uid_str not in seen:
|
| 303 |
-
accounts.append({'type': 'xtream', 'host': h, 'user': u, 'pass': p, '_uid': str(uuid.uuid4())[:8]}); seen.add(uid_str)
|
| 304 |
|
| 305 |
lines = [line.strip() for line in clean_text.split('\n') if line.strip()]
|
| 306 |
-
|
| 307 |
current_mac_host = None
|
| 308 |
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)
|
| 309 |
url_patt = re.compile(r'(https?://[^\s]+)')
|
|
@@ -311,13 +292,11 @@ def extract_all_links_from_text(text):
|
|
| 311 |
for line in lines:
|
| 312 |
u_m = url_patt.search(line.replace(" ", ""))
|
| 313 |
if u_m and 'username=' not in line: current_mac_host = u_m.group(1).rstrip('/')
|
| 314 |
-
|
| 315 |
m_m = mac_patt.search(line)
|
| 316 |
if m_m and current_mac_host:
|
| 317 |
m = m_m.group(1).upper().replace('-', ':')
|
| 318 |
uid_str = f"m_{current_mac_host}_{m}"
|
| 319 |
-
if uid_str not in seen:
|
| 320 |
-
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)
|
| 321 |
|
| 322 |
direct_links = re.findall(r'(https?://[^\s<>"\'\|]+)', clean_text, re.IGNORECASE)
|
| 323 |
for link in direct_links:
|
|
@@ -325,21 +304,19 @@ def extract_all_links_from_text(text):
|
|
| 325 |
if link not in seen and 'username=' not in link and not re.search(r'/(?:live|movie|series)/', link) and 'get.php?' not in link:
|
| 326 |
if any(x in link.lower() for x in ['.m3u8', '.ts', '.m3u', 'mpegts', '/stream/', 'amazonaws', '.dev', '.top', 'ok.ru', 'token=', 'index.', '/play/']):
|
| 327 |
uid_str = f"d_{link}"
|
| 328 |
-
if uid_str not in seen:
|
| 329 |
-
accounts.append({'type': 'direct', 'url': link, '_uid': str(uuid.uuid4())[:8]}); seen.add(uid_str); seen.add(link)
|
| 330 |
return accounts
|
| 331 |
|
| 332 |
# ==========================================
|
| 333 |
-
# 📡 ص
|
| 334 |
# ==========================================
|
| 335 |
def smart_mac_session(mac, change_ua=False):
|
| 336 |
session = requests.Session()
|
| 337 |
-
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0" if change_ua else "Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) MAG322 stbapp/5.0.1"
|
| 338 |
session.headers.update({"User-Agent": ua, "Cookie": f"mac={mac}; stb_lang=en; timezone=Europe/London;"})
|
| 339 |
retry_strategy = Retry(total=1, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["HEAD", "GET", "OPTIONS"], backoff_factor=0.5)
|
| 340 |
-
|
| 341 |
-
session.mount("
|
| 342 |
-
session.mount("https://", adapter)
|
| 343 |
return session
|
| 344 |
|
| 345 |
def clean_mac_address(mac): return mac.replace(':', '')
|
|
@@ -347,13 +324,13 @@ def clean_mac_address(mac): return mac.replace(':', '')
|
|
| 347 |
def test_mac_portal(host, mac):
|
| 348 |
session = smart_mac_session(mac)
|
| 349 |
conns, is_working, exp = "0 / 1", False, "Unlimited"
|
| 350 |
-
|
| 351 |
try:
|
| 352 |
r_hs = session.get(f"{host}/portal.php?type=stb&action=handshake&mac={mac}", timeout=5, verify=False).json()
|
| 353 |
-
if r_hs.get('js') and 'token' in r_hs['js']: session.headers.update({"Authorization": f"Bearer {r_hs['js']['token']}"})
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
|
|
|
| 357 |
is_working = True
|
| 358 |
conns = f"{prof.get('active_cons', '0')} / {prof.get('max_connections', '1')}"
|
| 359 |
for key in ['phone', 'expire_billing_date', 'expiry', 'expire', 'end_date', 'expiration', 'expiration_date', 'account_balance']:
|
|
@@ -366,12 +343,13 @@ def test_mac_portal(host, mac):
|
|
| 366 |
if exp == "Unlimited":
|
| 367 |
mac_clean = clean_mac_address(mac)
|
| 368 |
endpoints = [
|
| 369 |
-
f"/portal.php?action=get_main_info&type=account_info&mac={mac}",
|
|
|
|
|
|
|
| 370 |
f"/stalker_portal/server/api.php?action=get_subscription_info&mac={mac_clean}",
|
| 371 |
f"/stalker_portal/api/v2/player/get_subscription_info?mac={mac_clean}",
|
| 372 |
f"/server/api.php?action=get_subscription_info&mac={mac_clean}"
|
| 373 |
]
|
| 374 |
-
possible_keys = ['phone', 'expire_billing_date', 'expiry', 'expire', 'end_date', 'expiration', 'expiration_date']
|
| 375 |
for ep in endpoints:
|
| 376 |
try:
|
| 377 |
resp = session.get(f"{host.rstrip('/')}{ep}", timeout=5, verify=False)
|
|
@@ -381,19 +359,18 @@ def test_mac_portal(host, mac):
|
|
| 381 |
if json_match: raw = json_match.group(1)
|
| 382 |
try:
|
| 383 |
data = json.loads(raw)
|
|
|
|
| 384 |
js_data = data.get('js', data) if isinstance(data, dict) else data
|
| 385 |
if isinstance(js_data, dict):
|
| 386 |
-
for key in
|
| 387 |
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"]:
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
if date_match: exp = parse_timestamp(date_match.group(0)); return True, exp, conns
|
| 393 |
except: continue
|
| 394 |
return True, exp, conns
|
| 395 |
|
| 396 |
-
# 🔥 تطوير extract_mac_to_m3u_logic (نظام الذكاء المتعدد) 🔥
|
| 397 |
def extract_mac_to_m3u_logic(portal, mac, mode="🍿 الكل (لايف+أفلام+مسلسلات)", add_ua=False, ext_choice=".m3u8"):
|
| 398 |
session = smart_mac_session(mac)
|
| 399 |
try:
|
|
@@ -401,17 +378,15 @@ def extract_mac_to_m3u_logic(portal, mac, mode="🍿 الكل (لايف+أفلا
|
|
| 401 |
if hs.get('js') and 'token' in hs['js']: session.headers.update({"Authorization": f"Bearer {hs['js']['token']}"})
|
| 402 |
except: pass
|
| 403 |
|
| 404 |
-
st.write("🔄 جاري الاتصال بالبورتال واستخراج التصنيفات...")
|
| 405 |
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", [])
|
| 406 |
-
except: return False, "❌ فشل الاتصال بالبورتال
|
| 407 |
-
if not genres: return False, "❌ لم يتم العثور على تصنيفات
|
| 408 |
|
| 409 |
m3u_lines = ["#EXTM3U", "# Generated by Shalaby VIP Panel\n"]
|
| 410 |
total_channels = 0
|
| 411 |
ua_suffix = "|User-Agent=IPTVSmartersPro" if add_ua else ""
|
| 412 |
ext_val = "m3u8" if ext_choice == ".m3u8" else "ts" if ext_choice == ".ts" else ""
|
| 413 |
|
| 414 |
-
# المستوى الأول: اختبار حماية البورتال
|
| 415 |
strategy = "create_link"
|
| 416 |
first_genre_id = genres[0].get('id') if genres else None
|
| 417 |
if first_genre_id:
|
|
@@ -421,7 +396,6 @@ def extract_mac_to_m3u_logic(portal, mac, mode="🍿 الكل (لايف+أفلا
|
|
| 421 |
test_cmd = test_data[0].get("cmd", "") or test_data[0].get("id")
|
| 422 |
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()
|
| 423 |
if test_link.get("js") and test_link["js"].get("id") is None:
|
| 424 |
-
# Fallback Protocol: تجربة Token/Session جديدة
|
| 425 |
session = smart_mac_session(mac, change_ua=True)
|
| 426 |
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()
|
| 427 |
if test_link_2.get("js") and test_link_2["js"].get("id") is None: strategy = "raw_fallback"
|
|
@@ -447,12 +421,10 @@ def extract_mac_to_m3u_logic(portal, mac, mode="🍿 الكل (لايف+أفلا
|
|
| 447 |
name, cid, cmd_str = ch.get("name", "Unknown"), ch.get("id"), str(ch.get("cmd", ""))
|
| 448 |
if not cid or cid in seen: continue
|
| 449 |
seen.add(cid); added = True
|
| 450 |
-
|
| 451 |
if strategy == "create_link": stream_url = f"{portal}/portal.php?type=itv&action=create_link&cmd={urllib.parse.quote(cmd_str)}&mac={mac}"
|
| 452 |
-
else:
|
| 453 |
stream_url = f"{portal}/play/live.php?mac={mac}&stream={cid}"
|
| 454 |
if ext_val: stream_url += f"&extension={ext_val}"
|
| 455 |
-
|
| 456 |
logo = ch.get("logo", "")
|
| 457 |
if mode == "⚽ رياضة فقط VIP":
|
| 458 |
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})
|
|
@@ -469,8 +441,7 @@ def extract_mac_to_m3u_logic(portal, mac, mode="🍿 الكل (لايف+أفلا
|
|
| 469 |
else: m3u_lines.extend(res_l); total_channels += len(res_l)
|
| 470 |
if mode == "⚽ رياضة فقط VIP" and sports_channels:
|
| 471 |
sports_channels.sort(key=get_vip_sort_key)
|
| 472 |
-
for ch in sports_channels:
|
| 473 |
-
m3u_lines.append(f'#EXTINF:-1 tvg-logo="{ch["logo"]}" group-title="{ch["group"]}",{ch["name"]}\n{ch["url"]}{ua_suffix}'); total_channels += 1
|
| 474 |
|
| 475 |
if mode in ["🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"]:
|
| 476 |
try:
|
|
@@ -483,9 +454,8 @@ def extract_mac_to_m3u_logic(portal, mac, mode="🍿 الكل (لايف+أفلا
|
|
| 483 |
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", [])
|
| 484 |
if not vod_data: break
|
| 485 |
for vod in vod_data:
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
m3u_lines.append(f'#EXTINF:-1 tvg-logo="{logo}" group-title="Movies - {c_name}",{name}\n{stream_url}{ua_suffix}'); total_channels += 1
|
| 489 |
p += 1
|
| 490 |
except: break
|
| 491 |
except: pass
|
|
@@ -501,18 +471,17 @@ def extract_mac_to_m3u_logic(portal, mac, mode="🍿 الكل (لايف+أفلا
|
|
| 501 |
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", [])
|
| 502 |
if not ser_data: break
|
| 503 |
for ser in ser_data:
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
m3u_lines.append(f'#EXTINF:-1 tvg-logo="{logo}" group-title="Series - {c_name}",{name}\n{stream_url}{ua_suffix}'); total_channels += 1
|
| 507 |
p += 1
|
| 508 |
except: break
|
| 509 |
except: pass
|
| 510 |
|
| 511 |
-
if total_channels == 0: return False, "❌ لم يتم العثور على قنوات.", 0
|
| 512 |
return True, "\n".join(m3u_lines), total_channels
|
| 513 |
|
| 514 |
# ==========================================
|
| 515 |
-
# ✅ الفحص الذكي
|
| 516 |
# ==========================================
|
| 517 |
def check_server_smart(host, user, pwd):
|
| 518 |
headers = get_headers()
|
|
@@ -533,41 +502,35 @@ def check_server_smart(host, user, pwd):
|
|
| 533 |
except: pass
|
| 534 |
return None
|
| 535 |
|
| 536 |
-
# 🔥 قناص البنج الفعلي (يتخطى البنج الوهمي) 🔥
|
| 537 |
def test_live_stream_ping(host, user, pw):
|
| 538 |
try:
|
| 539 |
api_url = f"{host.rstrip('/')}/player_api.php?username={user}&password={pw}&action=get_live_streams"
|
| 540 |
r = requests.get(api_url, headers=get_headers(), timeout=5, verify=False).json()
|
| 541 |
if isinstance(r, list) and len(r) > 0:
|
| 542 |
-
|
| 543 |
-
test_url = f"{host.rstrip('/')}/live/{user}/{pw}/{stream_id}.ts"
|
| 544 |
res = requests.get(test_url, headers=get_headers(), stream=True, timeout=5, verify=False)
|
| 545 |
status = res.status_code
|
| 546 |
if status in [200, 302, 206]:
|
| 547 |
chunk = next(res.iter_content(chunk_size=128), b"")
|
| 548 |
res.close()
|
| 549 |
-
if b"<html" in chunk.lower() or b"error" in chunk.lower()[:20]: return False, "❌ السيرفر يعيد صفحة خطأ (بث متوقف)."
|
| 550 |
return True, "✅ يعمل بقوة."
|
| 551 |
res.close()
|
| 552 |
return False, f"❌ متوقف (كود {status})."
|
| 553 |
-
return False, "⚠️ الحساب يعمل لكن
|
| 554 |
except: return False, "❌ فشل الاتصال."
|
| 555 |
|
| 556 |
def advanced_direct_link_test(url):
|
| 557 |
-
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36'
|
| 558 |
try:
|
| 559 |
res = requests.get(url, headers=headers, stream=True, timeout=5, verify=False, allow_redirects=True)
|
| 560 |
if res.status_code in [200, 206, 302, 304]:
|
| 561 |
chunk = next(res.iter_content(chunk_size=2048), b"")
|
| 562 |
res.close()
|
| 563 |
text_chunk = chunk.decode('utf-8', errors='ignore').strip()
|
| 564 |
-
if text_chunk.startswith("#EXTM3U") or "#EXTINF" in text_chunk:
|
| 565 |
-
|
| 566 |
-
return True, True, count
|
| 567 |
-
lower_chunk = text_chunk.lower()
|
| 568 |
-
if "<html" in lower_chunk and not any(x in url for x in ['.dev', '.workers', '.top']): return False, False, 0
|
| 569 |
return True, False, 0
|
| 570 |
-
res.close()
|
| 571 |
except: pass
|
| 572 |
return False, False, 0
|
| 573 |
|
|
@@ -581,34 +544,20 @@ def process_single_account_sync(a):
|
|
| 581 |
if info:
|
| 582 |
d = calculate_days_left(info.get('exp_date'))
|
| 583 |
ping_ok, _ = test_live_stream_ping(a['host'], a['user'], a['pass'])
|
| 584 |
-
|
| 585 |
-
a.update({
|
| 586 |
-
'days': d, 'type_auth': info.get('source', 'Xtream'),
|
| 587 |
-
'created_at': parse_timestamp(info.get('created_at')),
|
| 588 |
-
'exp_date': parse_timestamp(info.get('exp_date')),
|
| 589 |
-
'status': 'يعمل بقوة' if ping_ok else 'متصل (البث متوقف)',
|
| 590 |
-
'ping_ok': ping_ok, 'formats': formats, 'conns': info.get('conns', 'N/A')
|
| 591 |
-
})
|
| 592 |
return a
|
| 593 |
elif a['type'] == 'mac':
|
| 594 |
prof_ok, exp, conns = test_mac_portal(a['host'], a['mac'])
|
| 595 |
if prof_ok:
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
'days': d, 'type_auth': 'Portal MAC',
|
| 599 |
-
'created_at': '-', 'exp_date': exp,
|
| 600 |
-
'status': 'يعمل بقوة', 'ping_ok': True, 'formats': 'Stalker/MAG', 'conns': conns
|
| 601 |
-
})
|
| 602 |
-
else:
|
| 603 |
-
a.update({'days': 'MAC', 'type_auth': 'Portal MAC', 'created_at': '-', 'exp_date': '-', 'status': 'لا يعمل', 'ping_ok': False, 'formats': 'Unknown', 'conns': 'N/A'})
|
| 604 |
return a
|
| 605 |
elif a['type'] == 'direct':
|
| 606 |
ping_ok, is_playlist, ch_count = advanced_direct_link_test(a['url'])
|
| 607 |
if ping_ok:
|
| 608 |
if is_playlist: a.update({'host': 'Playlist M3U', 'days': 'Direct', 'type_auth': 'Playlist', 'status': 'قائمة تشغيل تعمل', 'ping_ok': True, 'formats': 'm3u8/ts', 'ch_count': ch_count})
|
| 609 |
else: a.update({'host': 'Direct Link', 'days': 'Direct', 'type_auth': 'Stream', 'status': 'بث مباشر يعمل', 'ping_ok': True, 'formats': 'ts/m3u8'})
|
| 610 |
-
else:
|
| 611 |
-
a.update({'host': 'Direct Link', 'days': 'Direct', 'type_auth': 'Stream', 'status': 'لا يعمل', 'ping_ok': False, 'formats': 'Unknown'})
|
| 612 |
return a
|
| 613 |
except: pass
|
| 614 |
return None
|
|
@@ -630,17 +579,14 @@ async def run_async_scan(accs, progress_bar, status_msg, live_placeholder, conte
|
|
| 630 |
if result:
|
| 631 |
valid_found.append(result)
|
| 632 |
valid_found.sort(key=live_sort_key, reverse=True)
|
| 633 |
-
|
| 634 |
with live_placeholder.container():
|
| 635 |
for srv in valid_found:
|
| 636 |
ping_icon = "🟢" if srv.get('ping_ok') else "🔴"
|
| 637 |
-
|
| 638 |
-
days_str = f"{days_val} يوم" if str(days_val).isdigit() else str(days_val)
|
| 639 |
if srv.get('type') == 'direct': st.success(f"{ping_icon} {srv['status']} | 🌐 `{srv['url']}` | ⏳ {days_str}")
|
| 640 |
elif srv.get('type') == 'mac': st.success(f"{ping_icon} {srv['status']} | 🌐 `{srv['host']}` | 💳 `{srv['mac']}` | ⏳ {days_str}")
|
| 641 |
else: st.success(f"{ping_icon} {srv['status']} | 🌐 `{srv['host']}` | 👤 `{srv['user']}` | ⏳ {days_str}")
|
| 642 |
-
|
| 643 |
-
try:
|
| 644 |
if result.get('type') == 'xtream': save_to_global_vips({'host': result['host'], 'user': result['user'], 'pass': result['pass'], 'days': result.get('days')})
|
| 645 |
elif result.get('type') == 'mac': save_to_global_vips({'host': result['host'], 'mac': result['mac'], 'days': result.get('days'), 'type': 'mac'})
|
| 646 |
except: pass
|
|
@@ -657,19 +603,76 @@ def safe_async_run(coro):
|
|
| 657 |
asyncio.set_event_loop(new_loop)
|
| 658 |
result[0] = new_loop.run_until_complete(coro)
|
| 659 |
t = threading.Thread(target=_run)
|
| 660 |
-
t.start()
|
| 661 |
-
t.join()
|
| 662 |
return result[0]
|
| 663 |
else: return asyncio.run(coro)
|
| 664 |
|
| 665 |
-
def
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 670 |
|
| 671 |
# ==========================================
|
| 672 |
-
# 🎨 واجهة الكروت ال
|
| 673 |
# ==========================================
|
| 674 |
def render_server_card(server, idx, context="tab1"):
|
| 675 |
t = server.get('type')
|
|
@@ -678,308 +681,160 @@ def render_server_card(server, idx, context="tab1"):
|
|
| 678 |
uid_str = server.get('_uid', str(uuid.uuid4())[:8])
|
| 679 |
base_k = f"{context}_{idx}_{uid_str}"
|
| 680 |
|
| 681 |
-
force_open =
|
| 682 |
-
if 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 or f"preview_err_{base_k}" in st.session_state:
|
| 683 |
-
force_open = True
|
| 684 |
-
|
| 685 |
ping_icon = "🟢" if server.get('ping_ok') else "🔴"
|
| 686 |
-
status_str = server.get('status', 'نشط')
|
| 687 |
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
elif t == 'mac':
|
| 692 |
-
title = f"{ping_icon} {status_str} | 🌐 {server.get('host', '')} | 💳 {server.get('mac', '')} | 👥 {server.get('conns', '0 / 1')} | 🛑 {server.get('exp_date', 'Unlimited')} | ⏳ {days_str}"
|
| 693 |
-
else:
|
| 694 |
-
title = f"{ping_icon} {status_str} | 🌐 {server.get('host', '')} | 👤 {server.get('user', '')} | 🔑 {server.get('pass', '')} | 👥 {server.get('conns', 'N/A')} | 🛑 {server.get('exp_date', 'Unlimited')} | ⏳ {days_str}"
|
| 695 |
|
| 696 |
title += "\u200B" * st.session_state.reset_exp
|
| 697 |
|
| 698 |
with st.expander(title, expanded=force_open):
|
| 699 |
-
# 🔥 داخل البطاقة: رابط M3U المباشر في أول سطر 🔥
|
| 700 |
if t == 'xtream':
|
| 701 |
formatted_m3u_url = f"{server['host'].rstrip('/')}/get.php?username={server['user']}&password={server['pass']}&type=m3u_plus"
|
| 702 |
-
st.text_input("🔗
|
| 703 |
st.markdown("---")
|
| 704 |
-
st.markdown(f"### ⚙️ خيارات ال
|
| 705 |
-
|
| 706 |
elif t == 'direct':
|
| 707 |
-
st.text_input("🔗
|
| 708 |
st.markdown("---")
|
| 709 |
-
st.markdown(f"### ⚙️ خيارات التشغيل:")
|
| 710 |
-
|
| 711 |
-
elif t == 'mac':
|
| 712 |
-
st.markdown(f"### ⚙️ خيارات سحب الماك الذكية:")
|
| 713 |
|
| 714 |
-
# ================== أزرار التحكم ==================
|
| 715 |
if t == 'direct':
|
| 716 |
col_p1, col_p2 = st.columns(2)
|
| 717 |
with col_p1:
|
| 718 |
-
if server.get('type_auth') == 'Playlist':
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
except: st.error("❌ فشل الاتصال وسحب القائمة.")
|
| 728 |
with col_p2:
|
| 729 |
-
if st.button("📺
|
| 730 |
-
st.session_state[f"preview_url_{base_k}"] = server.get('url', '')
|
| 731 |
-
st.rerun()
|
| 732 |
|
| 733 |
elif t == 'mac':
|
| 734 |
col_mode, col_ext, col_ua, col_btn = st.columns([3, 2, 2, 2])
|
| 735 |
-
mac_mode = col_mode.selectbox("🎯 المحتوى:", ["⚽ رياضة فقط VIP", "📺 لايف فقط", "🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"], key=f"mode_{base_k}")
|
| 736 |
-
mac_ext = col_ext.selectbox("صيغة البث:", [".m3u8", ".ts", "بدون صيغة"], index=0, key=f"ext_{base_k}")
|
| 737 |
-
mac_ua = col_ua.checkbox("
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
with st.spinner("جاري سحب قنوات الماك الذكي (يُرجى الانتظار)..."):
|
| 741 |
ok, res_text, count = extract_mac_to_m3u_logic(server['host'], server['mac'], mac_mode, mac_ua, mac_ext)
|
| 742 |
-
if ok:
|
| 743 |
-
st.session_state[f"file_{base_k}"] = res_text.encode('utf-8')
|
| 744 |
-
st.session_state[f"count_{base_k}"] = count
|
| 745 |
-
st.session_state[f"filename_{base_k}"] = f"MAC_{server['mac'].replace(':','')}.m3u"
|
| 746 |
-
if f"prep_err_{base_k}" in st.session_state: del st.session_state[f"prep_err_{base_k}"]
|
| 747 |
else: st.session_state[f"prep_err_{base_k}"] = res_text
|
| 748 |
st.rerun()
|
| 749 |
|
| 750 |
elif t == 'xtream':
|
| 751 |
col_ping, col_preview = st.columns(2)
|
| 752 |
with col_ping:
|
| 753 |
-
if st.button("📡 فحص البث الفعلي ب
|
| 754 |
-
with st.spinner("جاري
|
| 755 |
ok, msg = test_live_stream_ping(server['host'], server['user'], server['pass'])
|
| 756 |
-
st.session_state[f"ping_res_{base_k}"] = (ok, msg)
|
| 757 |
-
st.rerun()
|
| 758 |
with col_preview:
|
| 759 |
-
if st.button("📺
|
| 760 |
-
with st.spinner("جاري
|
| 761 |
try:
|
| 762 |
-
|
| 763 |
-
r_streams
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
play_url = f"{server['host'].rstrip('/')}/live/{server['user']}/{server['pass']}/{s_id}.m3u8"
|
| 767 |
-
st.session_state[f"preview_url_{base_k}"] = play_url
|
| 768 |
-
else: st.session_state[f"preview_err_{base_k}"] = "لم يتم العثور على قنوات للمعاينة."
|
| 769 |
-
except: st.session_state[f"preview_err_{base_k}"] = "فشل الاتصال لجلب البث."
|
| 770 |
st.rerun()
|
| 771 |
|
| 772 |
if f"ping_res_{base_k}" in st.session_state:
|
| 773 |
ok, msg = st.session_state[f"ping_res_{base_k}"]
|
| 774 |
if ok: st.success(msg)
|
| 775 |
else: st.error(msg)
|
| 776 |
-
if f"preview_err_{base_k}" in st.session_state:
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
f.write("#EXTM3U\n")
|
| 789 |
-
if mode == "⚽ رياضة فقط VIP":
|
| 790 |
-
api_url = f"{host}/player_api.php?username={user}&password={pw}"
|
| 791 |
-
try:
|
| 792 |
-
streams = requests.get(f"{api_url}&action=get_live_streams", timeout=25, verify=False).json()
|
| 793 |
-
cats = requests.get(f"{host}/player_api.php?username={user}&password={pw}&action=get_live_categories", headers=headers, timeout=25, verify=False).json()
|
| 794 |
-
cat_map = {i['category_id']: i['category_name'] for i in cats}
|
| 795 |
-
domain_parts = host.split('//')[-1].split(':')[0].split('.')
|
| 796 |
-
valid_parts = [p for p in domain_parts if p.lower() not in ['com', 'net', 'org', 'info', 'tv'] and len(p)>2]
|
| 797 |
-
sn = re.sub(r'[^a-zA-Z0-9]', '', valid_parts[0].capitalize() if valid_parts else "Server")
|
| 798 |
-
vip_channels = []
|
| 799 |
-
for s in streams:
|
| 800 |
-
name, cat = str(s.get('name', '')), str(cat_map.get(s.get('category_id'), "Other"))
|
| 801 |
-
if is_arabic_sport(name, cat):
|
| 802 |
-
chic_name = f"{name} [{sn}]"
|
| 803 |
-
vip_channels.append({"name": chic_name, "group": categorize_vip_sport(name, sn), "logo": get_vip_logo(name) or s.get("stream_icon", ""), "url": f"{host}/live/{user}/{pw}/{s.get('stream_id')}{live_ext}"})
|
| 804 |
-
vip_channels.sort(key=get_vip_sort_key)
|
| 805 |
-
for ch in vip_channels:
|
| 806 |
-
f.write(f'#EXTINF:-1 tvg-logo="{ch["logo"]}" group-title="{ch["group"]}",{ch["name"]}\n{ch["url"]}{ua_suffix}\n'); extracted_count += 1
|
| 807 |
-
except: pass
|
| 808 |
-
else:
|
| 809 |
-
if mode in ["📺 لايف فقط", "🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"]:
|
| 810 |
-
try:
|
| 811 |
-
with urllib.request.urlopen(urllib.request.Request(f"{host}/player_api.php?username={user}&password={pw}&action=get_live_streams", headers=headers), timeout=30) as resp:
|
| 812 |
-
data = json.loads(resp.read().decode('utf-8'))
|
| 813 |
-
if isinstance(data, list):
|
| 814 |
-
for ch in data: f.write(f'#EXTINF:-1 tvg-logo="{ch.get("stream_icon", "")}" group-title="Live TV",{ch.get("name", "Unknown Live")}\n{host}/live/{user}/{pw}/{ch.get("stream_id", "")}{live_ext}{ua_suffix}\n'); extracted_count += 1
|
| 815 |
-
except: pass
|
| 816 |
-
if mode in ["🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"]:
|
| 817 |
-
try:
|
| 818 |
-
with urllib.request.urlopen(urllib.request.Request(f"{host}/player_api.php?username={user}&password={pw}&action=get_vod_streams", headers=headers), timeout=30) as resp:
|
| 819 |
-
data = json.loads(resp.read().decode('utf-8'))
|
| 820 |
-
if isinstance(data, list):
|
| 821 |
-
for mov in data: f.write(f'#EXTINF:-1 tvg-logo="{mov.get("stream_icon", "")}" group-title="Movies",{mov.get("name", "Unknown Movie")}\n{host}/movie/{user}/{pw}/{mov.get("stream_id", "")}.{mov.get("container_extension", "mp4")}{ua_suffix}\n'); extracted_count += 1
|
| 822 |
-
except: pass
|
| 823 |
-
if mode == "🍿 الكل (لايف+أفلام+مسلسلات)":
|
| 824 |
-
try:
|
| 825 |
-
with urllib.request.urlopen(urllib.request.Request(f"{host}/player_api.php?username={user}&password={pw}&action=get_series", headers=headers), timeout=30) as resp:
|
| 826 |
-
data = json.loads(resp.read().decode('utf-8'))
|
| 827 |
-
if isinstance(data, list):
|
| 828 |
-
for ser in data: f.write(f'#EXTINF:-1 tvg-logo="{ser.get("cover", "")}" group-title="Series",{ser.get("name", "Unknown Series")} (Series)\n{host}/series/{user}/{pw}/{ser.get("series_id", "")}{live_ext}{ua_suffix}\n'); extracted_count += 1
|
| 829 |
-
except: pass
|
| 830 |
-
return True if extracted_count > 0 else False, output_file, extracted_count
|
| 831 |
-
|
| 832 |
-
col_mode, col_ext, col_ua, col_btn = st.columns([3, 2, 2, 2])
|
| 833 |
-
selected_mode = col_mode.selectbox("🎯 المحتوى:", ["⚽ رياضة فقط VIP", "📺 لايف فقط", "🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"], key=f"mode_{base_k}")
|
| 834 |
-
selected_ext = col_ext.selectbox("صيغة البث:", [".m3u8", ".ts", "بدون صيغة"], key=f"ext_{base_k}")
|
| 835 |
-
add_ua = col_ua.checkbox("إضافة حماية UA", value=False, key=f"ua_{base_k}")
|
| 836 |
-
|
| 837 |
-
if col_btn.button("📥 تجهيز السحب", key=f"prep_btn_{base_k}"):
|
| 838 |
-
with st.spinner("جاري سحب المحتوى..."):
|
| 839 |
-
success, file, count = custom_api_extractor_sync(server['host'], server['user'], server['pass'], selected_mode, selected_ext, add_ua)
|
| 840 |
-
if success:
|
| 841 |
-
st.session_state[f"file_{base_k}"] = open(file, 'rb').read()
|
| 842 |
-
st.session_state[f"count_{base_k}"] = count
|
| 843 |
-
st.session_state[f"filename_{base_k}"] = f"VIP_{server['user']}_{selected_mode.split()[0]}.m3u"
|
| 844 |
-
if f"prep_err_{base_k}" in st.session_state: del st.session_state[f"prep_err_{base_k}"]
|
| 845 |
-
else: st.session_state[f"prep_err_{base_k}"] = "❌ فشل السحب أو المحتوى فارغ."
|
| 846 |
-
st.rerun()
|
| 847 |
-
|
| 848 |
-
if f"prep_err_{base_k}" in st.session_state: st.error(st.session_state[f"prep_err_{base_k}"])
|
| 849 |
|
| 850 |
-
# واجهة مشغل المعاينة المدمج
|
| 851 |
if f"preview_url_{base_k}" in st.session_state:
|
| 852 |
p_url = st.session_state[f"preview_url_{base_k}"]
|
| 853 |
-
import urllib.parse
|
| 854 |
hls_demo = f"https://hls-js.netlify.app/demo/?src={urllib.parse.quote(p_url)}"
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
player_html = f"""
|
| 859 |
-
<link href="https://vjs.zencdn.net/7.11.4/video-js.css" rel="stylesheet" />
|
| 860 |
-
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
|
| 861 |
-
<video id="my-video" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto" width="100%" height="250" data-setup='{{}}'>
|
| 862 |
-
<source src="{p_url}" type="application/x-mpegURL">
|
| 863 |
-
</video>
|
| 864 |
-
"""
|
| 865 |
-
components.html(player_html, height=270)
|
| 866 |
-
|
| 867 |
-
st.markdown(f"""
|
| 868 |
-
<div style="background-color: #2b2b2b; padding: 15px; border-radius: 8px; text-align: center; border: 1px solid #444; margin-bottom: 15px;">
|
| 869 |
-
<p style="color: #ff9800; font-weight: bold; font-size: 14px; margin-bottom: 10px;">🛡️ تخطي حماية المتصفح وتشغيل البث الخارجي:</p>
|
| 870 |
-
<a href="{android_intent}" style="text-decoration: none;">
|
| 871 |
-
<button style="background-color: #00cc66; color: white; border: none; padding: 11px 18px; border-radius: 6px; font-weight: bold; cursor: pointer; margin: 5px; box-shadow: 0px 4px 6px rgba(0,0,0,0.2);">
|
| 872 |
-
📱 فتح برامج الهاتف (VLC / MX Player)
|
| 873 |
-
</button>
|
| 874 |
-
</a>
|
| 875 |
-
<a href="{hls_demo}" target="_blank" style="text-decoration: none;">
|
| 876 |
-
<button style="background-color: #ff9800; color: white; border: none; padding: 11px 18px; border-radius: 6px; font-weight: bold; cursor: pointer; margin: 5px;">
|
| 877 |
-
🌐 مشغل ويب خارجي (Netlify)
|
| 878 |
-
</button>
|
| 879 |
-
</a>
|
| 880 |
-
</div>
|
| 881 |
-
""", unsafe_allow_html=True)
|
| 882 |
-
|
| 883 |
-
# واجهة عرض وتنزيل الملفات
|
| 884 |
if f"file_{base_k}" in st.session_state:
|
| 885 |
-
st.success(f"✅ تم سحب ({st.session_state[f'count_{base_k}']}
|
| 886 |
-
st.download_button("💾 تحميل
|
| 887 |
-
|
| 888 |
-
with st.expander("🔍 بحث ونسخ القنوات المسحوبة (سريع ومضمون)", expanded=True):
|
| 889 |
raw_text = st.session_state[f"file_{base_k}"].decode('utf-8', errors='ignore')
|
| 890 |
-
st.info("💡 اضغط داخل الصندوق لنسخ القنوات. للفلترة استخدم صندوق البحث بالأعلى:")
|
| 891 |
-
|
| 892 |
col_s1, col_s2 = st.columns([3, 1])
|
| 893 |
-
search_q = col_s1.text_input("🔍 اب
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
display_text = raw_text
|
| 897 |
-
if search_clicked or search_q:
|
| 898 |
if search_q.strip():
|
| 899 |
-
lines = raw_text.splitlines()
|
| 900 |
-
filtered = ["#EXTM3U"]
|
| 901 |
for i in range(len(lines)):
|
| 902 |
-
if lines[i].startswith("#EXTINF"):
|
| 903 |
-
|
| 904 |
-
|
| 905 |
-
|
| 906 |
-
|
| 907 |
-
|
| 908 |
-
found_count = (len(filtered) - 1) // 2
|
| 909 |
-
if found_count > 0:
|
| 910 |
-
st.success(f"🎯 وجدنا ({found_count}) قناة! اضغط داخل المربع الأبيض ⬇️ واختر (تحديد الكل) ثم (نسخ).")
|
| 911 |
-
display_text = "\n".join(filtered)
|
| 912 |
-
else:
|
| 913 |
-
st.error("❌ لم يتم العثور على قنوات بهذا الاسم.")
|
| 914 |
-
display_text = ""
|
| 915 |
-
else: st.warning("يرجى كتابة كلمة للبحث.")
|
| 916 |
-
|
| 917 |
-
st.text_area("محتوى الملف (اضغط ضغطة مطولة -> تحديد الكل -> نسخ):", value=display_text, height=300, key=f"all_{base_k}")
|
| 918 |
|
| 919 |
st.markdown("---")
|
| 920 |
if context == "saved":
|
| 921 |
-
if st.button("🗑️ حذف الس
|
| 922 |
svs = load_servers(); svs.pop(idx); save_all_servers_to_db(svs)
|
| 923 |
-
st.success("تم الحذف
|
| 924 |
else:
|
| 925 |
-
if t == 'xtream':
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
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'))
|
| 932 |
-
st.success("✅ تم حفظ الماك بنجاح!")
|
| 933 |
|
| 934 |
# ==========================================
|
| 935 |
-
# 🤖
|
| 936 |
# ==========================================
|
| 937 |
@bot.message_handler(commands=['start', 'menu'])
|
| 938 |
def send_menu(message):
|
| 939 |
-
save_admin(message.chat.id)
|
| 940 |
-
bot.send_message(message.chat.id, "📺 **Shalaby IPTV Tool**\nأهلاً بك يا هندسة!", parse_mode="Markdown")
|
| 941 |
|
| 942 |
@bot.message_handler(commands=['allservers'])
|
| 943 |
def send_all_servers(message):
|
| 944 |
vips = load_global_vips()
|
| 945 |
-
if not vips:
|
| 946 |
-
bot.reply_to(message, "📭 الصندوق الأسود فارغ حالياً.")
|
| 947 |
-
return
|
| 948 |
file_path = "All_Hunted_Servers.txt"
|
| 949 |
with open(file_path, "w", encoding="utf-8") as f:
|
| 950 |
-
f.write(f"--- 📦 إجمالي السيرفرات المصادة: {len(vips)} ---\n\n")
|
| 951 |
for s in vips:
|
| 952 |
if s.get('type') == 'mac': f.write(f"Portal: {s.get('host')}\nMAC: {s.get('mac')}\nDays Left: {s.get('days')}\n")
|
| 953 |
else: f.write(f"Host: {s.get('host')}\nUser: {s.get('user')}\nPass: {s.get('pass')}\nDays Left: {s.get('days')}\n")
|
| 954 |
f.write("-" * 30 + "\n")
|
| 955 |
-
with open(file_path, "rb") as doc: bot.send_document(message.chat.id, doc, caption=f"🔥 تفضل يا هندسة، إجمالي السيرفرات المصادة ({len(vips)}).")
|
| 956 |
|
| 957 |
@bot.message_handler(func=lambda message: True)
|
| 958 |
def handle_all_messages(message):
|
| 959 |
-
|
| 960 |
-
accs = extract_all_links_from_text(text)
|
| 961 |
if accs:
|
| 962 |
-
msg = bot.reply_to(message, "🔍 جاري فحص السيرفرات المرسلة...")
|
| 963 |
valid = []
|
| 964 |
for a in accs:
|
| 965 |
if a['type'] == 'xtream':
|
| 966 |
info = check_server_smart(a['host'], a['user'], a['pass'])
|
| 967 |
-
if info:
|
| 968 |
-
days = calculate_days_left(info.get('exp_date'))
|
| 969 |
-
days_str = f"{days} يوم" if str(days).isdigit() else str(days)
|
| 970 |
-
valid.append(f"✅ `{a['host']}` | 👤 `{a['user']}` | ⏳ {days_str}")
|
| 971 |
-
try: save_to_global_vips({'type': 'xtream', 'host': a['host'], 'user': a['user'], 'pass': a['pass'], 'days': days})
|
| 972 |
-
except: pass
|
| 973 |
elif a['type'] == 'mac':
|
| 974 |
prof_ok, exp, _ = test_mac_portal(a['host'], a['mac'])
|
| 975 |
-
if prof_ok:
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
valid.append(f"✅ `{a['host']}` | 💳 `{a['mac']}` | ⏳ {days_str}")
|
| 979 |
-
try: save_to_global_vips({'type': 'mac', 'host': a['host'], 'mac': a['mac'], 'days': days})
|
| 980 |
-
except: pass
|
| 981 |
-
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")
|
| 982 |
-
else: bot.edit_message_text("❌ لم أجد أي سيرفرات تعمل.", chat_id=msg.chat.id, message_id=msg.message_id)
|
| 983 |
|
| 984 |
def run_bot_thread_init():
|
| 985 |
for t in threading.enumerate():
|
|
@@ -987,162 +842,108 @@ def run_bot_thread_init():
|
|
| 987 |
def start_polling():
|
| 988 |
try: requests.get(f"https://api.telegram.org/bot{TOKEN}/deleteWebhook?drop_pending_updates=True", timeout=5)
|
| 989 |
except: pass
|
| 990 |
-
time.sleep(2)
|
| 991 |
-
|
| 992 |
-
t = threading.Thread(target=start_polling, name="TelegramBotThread", daemon=True)
|
| 993 |
-
t.start()
|
| 994 |
run_bot_thread_init()
|
| 995 |
|
| 996 |
st.set_page_config(page_title="Shalaby IPTV Tool", page_icon="📺", layout="wide")
|
| 997 |
|
| 998 |
st.markdown("""
|
| 999 |
<style>
|
| 1000 |
-
.block-container {
|
| 1001 |
-
|
| 1002 |
-
|
| 1003 |
-
|
| 1004 |
-
padding-right: 1rem !important;
|
| 1005 |
-
padding-bottom: 5rem !important;
|
| 1006 |
-
}
|
| 1007 |
-
section[data-testid="stSidebar"] {
|
| 1008 |
-
width: 170px !important;
|
| 1009 |
-
min-width: 170px !important;
|
| 1010 |
-
max-width: 170px !important;
|
| 1011 |
-
}
|
| 1012 |
-
.main { overflow: visible !important; text-align: right; direction: rtl; }
|
| 1013 |
-
h1 { font-size: 1.8rem !important; margin-bottom: 0 !important; padding-bottom: 0.5rem !important; text-align: center; color: #ff4b4b;}
|
| 1014 |
div[data-baseweb="tab-list"] { display: flex; flex-direction: column; gap: 8px; }
|
| 1015 |
div[data-baseweb="tab"] { width: 100%; justify-content: flex-end; background-color: #2b2b2b; border-radius: 8px; padding: 10px !important; border: 1px solid #444;}
|
| 1016 |
div[data-baseweb="tab"]:hover { background-color: #ff4b4b; color: white !important;}
|
| 1017 |
.stButton>button { width: 100%; border-radius: 8px; font-weight: bold; background-color: #ff4b4b; color: white; margin-top: 5px;}
|
| 1018 |
-
.stButton>button:hover { background-color: #ff3333; color: white; border-color: #ff3333; }
|
| 1019 |
|
| 1020 |
[data-testid="stExpander"] details summary {
|
| 1021 |
-
background-color: #262730 !important;
|
| 1022 |
-
border-left: 5px solid #ff9800 !important;
|
| 1023 |
-
border-right: none !important;
|
| 1024 |
-
border-radius: 8px !important;
|
| 1025 |
-
padding: 12px !important;
|
| 1026 |
-
margin-bottom: 5px !important;
|
| 1027 |
-
box-shadow: 0px 4px 6px rgba(0,0,0,0.3) !important;
|
| 1028 |
-
transition: all 0.3s ease !important;
|
| 1029 |
-
}
|
| 1030 |
-
[data-testid="stExpander"] details summary p,
|
| 1031 |
-
[data-testid="stExpander"] details summary span {
|
| 1032 |
-
color: #ffffff !important;
|
| 1033 |
-
font-weight: bold !important;
|
| 1034 |
-
}
|
| 1035 |
-
[data-testid="stExpander"] details[open] summary {
|
| 1036 |
-
background-color: #333333 !important;
|
| 1037 |
-
border-left: 5px solid #ff9800 !important;
|
| 1038 |
}
|
| 1039 |
[data-testid="stExpander"] details[open] > div {
|
| 1040 |
-
background-color: #1e1e1e !important;
|
| 1041 |
-
border: 1px solid #444 !important;
|
| 1042 |
-
border-top: none !important;
|
| 1043 |
-
border-radius: 0 0 10px 10px !important;
|
| 1044 |
-
padding: 20px 40px 20px 20px !important;
|
| 1045 |
-
margin-top: -5px !important;
|
| 1046 |
-
margin-left: 0px !important;
|
| 1047 |
-
margin-right: 40px !important;
|
| 1048 |
-
width: calc(100% - 40px) !important;
|
| 1049 |
-
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5) !important;
|
| 1050 |
-
}
|
| 1051 |
-
[data-testid="stExpander"] details[open] > div p,
|
| 1052 |
-
[data-testid="stExpander"] details[open] > div span,
|
| 1053 |
-
[data-testid="stExpander"] details[open] > div label {
|
| 1054 |
-
color: #e0e0e0 !important;
|
| 1055 |
-
}
|
| 1056 |
-
[data-testid="stExpander"] .stTextInput input,
|
| 1057 |
-
[data-testid="stExpander"] .stTextArea textarea {
|
| 1058 |
-
background-color: #2b2b2b !important;
|
| 1059 |
-
color: #ffffff !important;
|
| 1060 |
-
border: 1px solid #555 !important;
|
| 1061 |
}
|
| 1062 |
</style>
|
| 1063 |
""", unsafe_allow_html=True)
|
| 1064 |
|
|
|
|
| 1065 |
if not st.session_state.logged_in:
|
| 1066 |
-
st.title("🔐 بوابة الدخول - Shalaby IPTV Tool")
|
| 1067 |
-
tab_in, tab_up = st.tabs(["تسجيل دخول", "إنشاء
|
| 1068 |
with tab_up:
|
| 1069 |
-
new_user = st.text_input("اختر كود
|
| 1070 |
-
if st.button("🚀 إنشاء
|
| 1071 |
if new_user.strip():
|
| 1072 |
users = load_users_registry()
|
| 1073 |
if new_user.strip() not in users:
|
| 1074 |
users.append(new_user.strip()); save_users_registry(users)
|
| 1075 |
-
st.session_state.username
|
| 1076 |
st.query_params["user_auth"] = new_user.strip()
|
| 1077 |
components.html(f"""<script>localStorage.setItem("user_auth", "{new_user.strip()}");</script>""", height=0, width=0)
|
| 1078 |
time.sleep(0.5); st.rerun()
|
| 1079 |
-
else: st.error("❌
|
| 1080 |
with tab_in:
|
| 1081 |
-
exist_user = st.text_input("أدخل
|
| 1082 |
-
if st.button("🔓 دخول"):
|
| 1083 |
if exist_user.strip() in load_users_registry():
|
| 1084 |
-
st.session_state.username
|
| 1085 |
st.query_params["user_auth"] = exist_user.strip()
|
| 1086 |
components.html(f"""<script>localStorage.setItem("user_auth", "{exist_user.strip()}");</script>""", height=0, width=0)
|
| 1087 |
time.sleep(0.5); st.rerun()
|
| 1088 |
-
else: st.error("❌
|
| 1089 |
st.stop()
|
| 1090 |
|
| 1091 |
with st.sidebar:
|
| 1092 |
-
st.markdown(f"### 👤
|
| 1093 |
-
st.success("✅ لوحة تحكمك جاهزة.")
|
| 1094 |
if st.button("🚪 تسجيل خروج"):
|
| 1095 |
components.html("""<script>localStorage.removeItem("user_auth");</script>""", height=0, width=0)
|
| 1096 |
-
st.query_params.clear(); st.session_state.logged_in = False;
|
| 1097 |
|
| 1098 |
-
st.title("📺 Shalaby
|
| 1099 |
st.markdown("---")
|
| 1100 |
|
| 1101 |
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
|
| 1102 |
-
"🧪
|
| 1103 |
])
|
| 1104 |
|
| 1105 |
with tab1:
|
| 1106 |
-
st.markdown("### 🔍 أدخل ال
|
| 1107 |
if 'valid_found_tab1' in st.session_state and st.session_state['valid_found_tab1']:
|
| 1108 |
-
if st.button("🔽
|
| 1109 |
st.session_state.reset_exp += 1
|
| 1110 |
for k in list(st.session_state.keys()):
|
| 1111 |
if k.startswith("file_") or k.startswith("ping_res_") or k.startswith("prep_err_") or k.startswith("preview_"): del st.session_state[k]
|
| 1112 |
st.rerun()
|
| 1113 |
|
| 1114 |
-
bulk_input = st.text_area("النص المبعثر:", height=150)
|
| 1115 |
-
if st.button("🚀
|
| 1116 |
-
if not bulk_input.strip(): st.warning("⚠️
|
| 1117 |
else:
|
| 1118 |
accs = extract_all_links_from_text(bulk_input)
|
| 1119 |
-
|
| 1120 |
-
if total_to_check == 0: st.error("❌ لم يتم العثور على روابط أو سيرفرات صالحة.")
|
| 1121 |
else:
|
| 1122 |
-
|
| 1123 |
-
|
| 1124 |
-
|
| 1125 |
-
|
| 1126 |
-
|
| 1127 |
-
|
| 1128 |
-
|
| 1129 |
-
|
| 1130 |
-
|
| 1131 |
-
for v in valid_found:
|
| 1132 |
-
if v.get('type') == 'xtream' and not any(s.get('host') == v['host'] and s.get('user') == v['user'] for s in current_db):
|
| 1133 |
-
current_db.append(v)
|
| 1134 |
-
save_all_servers_to_db(current_db)
|
| 1135 |
|
| 1136 |
if 'valid_found_tab1' in st.session_state and st.session_state['valid_found_tab1']:
|
| 1137 |
-
st.markdown("#### ⚙️ ال
|
| 1138 |
for i, acc in enumerate(st.session_state['valid_found_tab1']): render_server_card(acc, i, context="t1")
|
| 1139 |
|
| 1140 |
with tab2:
|
| 1141 |
-
st.markdown("### 🗄️
|
| 1142 |
svs = load_servers()
|
| 1143 |
-
if not svs: st.warning("لا
|
| 1144 |
else:
|
| 1145 |
-
if st.button("🔽
|
| 1146 |
st.session_state.reset_exp += 1
|
| 1147 |
for k in list(st.session_state.keys()):
|
| 1148 |
if k.startswith("file_") or k.startswith("ping_res_") or k.startswith("prep_err_") or k.startswith("preview_"): del st.session_state[k]
|
|
@@ -1150,109 +951,82 @@ with tab2:
|
|
| 1150 |
for i, s in enumerate(svs): render_server_card(s, i, context="saved")
|
| 1151 |
|
| 1152 |
with tab3:
|
| 1153 |
-
st.markdown("### 🚀
|
| 1154 |
-
if st.button("🔄 بدء الت
|
| 1155 |
svs = load_servers()
|
| 1156 |
-
if not svs: st.error("❌ ل
|
| 1157 |
else:
|
| 1158 |
-
|
| 1159 |
for i, s in enumerate(svs):
|
| 1160 |
-
if s.get('type')
|
| 1161 |
-
|
| 1162 |
-
f_db = load_db()
|
| 1163 |
-
|
| 1164 |
-
st.
|
| 1165 |
-
with open(m3u_file, "rb") as file: st.download_button(label="💾 تحميل اللستة المحدثة", data=file, file_name=f"Updated_Shalaby_{st.session_state.username}.m3u", mime="audio/x-mpegurl")
|
| 1166 |
|
| 1167 |
with tab4:
|
| 1168 |
-
st.markdown("### 🏭
|
| 1169 |
-
|
| 1170 |
-
if not
|
| 1171 |
else:
|
| 1172 |
-
|
| 1173 |
-
|
| 1174 |
-
|
| 1175 |
-
|
| 1176 |
-
|
| 1177 |
-
if not selected_groups: st.error("❌ الرجاء اختيار باقة واحدة على الأقل.")
|
| 1178 |
else:
|
| 1179 |
-
|
| 1180 |
-
|
| 1181 |
-
|
| 1182 |
-
|
| 1183 |
-
|
| 1184 |
-
|
| 1185 |
-
|
| 1186 |
-
|
| 1187 |
-
|
| 1188 |
-
|
| 1189 |
-
with open(custom_file, "rb") as file: st.download_button(label="📥 تحميل اللستة المخصصة", data=file, file_name=f"Custom_VIP_{st.session_state.username}.m3u", mime="audio/x-mpegurl")
|
| 1190 |
-
else: st.error("❌ لم يتم العثور على قنوات.")
|
| 1191 |
|
| 1192 |
with tab5:
|
| 1193 |
-
st.markdown("### 🔥 ا
|
| 1194 |
-
|
| 1195 |
-
|
| 1196 |
-
|
| 1197 |
-
|
| 1198 |
-
|
| 1199 |
-
|
| 1200 |
-
|
| 1201 |
-
|
| 1202 |
-
|
| 1203 |
-
if col_b.button("🚀 بدء الاختراق والتحويل"):
|
| 1204 |
-
if not mac_portal or not mac_address: st.warning("⚠️ الرجاء إدخال البورت والماك.")
|
| 1205 |
else:
|
| 1206 |
-
|
| 1207 |
-
|
| 1208 |
-
|
| 1209 |
-
|
| 1210 |
-
|
| 1211 |
-
st.
|
| 1212 |
-
|
| 1213 |
-
else: st.error(result)
|
| 1214 |
|
| 1215 |
with tab6:
|
| 1216 |
-
st.markdown("### ✨ ساحب
|
| 1217 |
if 'valid_found_tab6' in st.session_state and st.session_state['valid_found_tab6']:
|
| 1218 |
-
if st.button("🔽
|
| 1219 |
st.session_state.reset_exp += 1
|
| 1220 |
for k in list(st.session_state.keys()):
|
| 1221 |
if k.startswith("file_") or k.startswith("ping_res_") or k.startswith("prep_err_") or k.startswith("preview_"): del st.session_state[k]
|
| 1222 |
st.rerun()
|
| 1223 |
|
| 1224 |
-
ex_input = st.text_area("
|
| 1225 |
-
if st.button("⚙️
|
| 1226 |
-
if not ex_input.strip(): st.warning("⚠️
|
| 1227 |
else:
|
| 1228 |
accs = extract_all_links_from_text(ex_input)
|
| 1229 |
-
if not accs: st.error("❌ لم يتم العثور على روابط
|
| 1230 |
else:
|
| 1231 |
-
|
| 1232 |
-
|
| 1233 |
-
|
| 1234 |
-
|
| 1235 |
-
status_msg.success(f"🎉 اكتمل الفحص بصاروخ Asyncio! وجدنا {len(valid_accs)} سيرفرات/روابط.")
|
| 1236 |
-
st.session_state['valid_found_tab6'] = valid_accs
|
| 1237 |
-
|
| 1238 |
-
if valid_accs:
|
| 1239 |
-
current_db = load_servers()
|
| 1240 |
-
for v in valid_accs:
|
| 1241 |
-
if v.get('type') == 'xtream' and not any(s.get('host') == v['host'] and s.get('user') == v['user'] for s in current_db):
|
| 1242 |
-
current_db.append(v)
|
| 1243 |
-
save_all_servers_to_db(current_db)
|
| 1244 |
|
| 1245 |
if 'valid_found_tab6' in st.session_state and st.session_state['valid_found_tab6']:
|
| 1246 |
st.markdown("#### ⚙️ الكروت النهائية بعد الفحص والترتيب التلقائي:")
|
| 1247 |
for i, acc in enumerate(st.session_state['valid_found_tab6']): render_server_card(acc, i, context="t6")
|
| 1248 |
-
|
| 1249 |
-
st.markdown("---")
|
| 1250 |
-
with st.expander("🔬 فحص رابط قناة مفردة (اختبار الشاشة السوداء)"):
|
| 1251 |
-
single_link = st.text_input("🔗 أدخل رابط القناة (m3u8 أو ts):", placeholder="http://host.com/live/user/pass/123.m3u8")
|
| 1252 |
-
if st.button("⚡ فحص استجابة القناة"):
|
| 1253 |
-
if single_link.strip():
|
| 1254 |
-
with st.spinner("جاري فحص القناة..."):
|
| 1255 |
-
ping_ok, _, _ = advanced_direct_link_test(single_link.strip())
|
| 1256 |
-
if ping_ok: st.success("✅ القناة تعمل بكفاءة (تسحب بيانات).")
|
| 1257 |
-
else: st.error("❌ القناة لا تستجيب (قد تكون شاشة سوداء أو محمية بشدة).")
|
| 1258 |
-
else: st.warning("الرجاء إدخال الرابط أولاً.")
|
|
|
|
| 95 |
with open(registry_file, 'w', encoding='utf-8') as f: json.dump(users_list, f)
|
| 96 |
sync_file_to_cloud(registry_file)
|
| 97 |
|
| 98 |
+
# ==========================================
|
| 99 |
+
# 📅 معالج التواريخ الذكي
|
| 100 |
+
# ==========================================
|
| 101 |
def parse_timestamp(ts):
|
| 102 |
if not ts or str(ts).strip() == "": return "Unlimited"
|
| 103 |
ts_str = str(ts).strip()
|
|
|
|
| 132 |
def save_admin(chat_id):
|
| 133 |
with open(ADMIN_FILE, "w") as f: f.write(str(chat_id))
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
def load_servers():
|
| 136 |
file_name = get_user_servers_file()
|
| 137 |
sync_file_from_cloud(file_name)
|
|
|
|
| 214 |
t = str(text).lower()
|
| 215 |
t = re.sub(r'\b(بين|بي ان|be in)\b', 'bein', t)
|
| 216 |
t = re.sub(r'\b(sports|sport|سبورت|رياضة|رياضه)\b', 'sport', t)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
t = re.sub(r'[^a-z0-9\sأ-ي]', '', t)
|
| 218 |
return t
|
| 219 |
|
|
|
|
| 253 |
rest = re.split(r'/(?:live|movie|series)/', url, 1)[1]
|
| 254 |
rest_clean = re.sub(r'/+', '/', rest).strip('/')
|
| 255 |
parts = rest_clean.split('/')
|
| 256 |
+
if len(parts) >= 2: return host, parts[0], parts[1]
|
|
|
|
| 257 |
except: pass
|
| 258 |
return None, None, None
|
| 259 |
|
| 260 |
+
def clean_camouflage(text): return re.sub(r'[@#]+', '', text)
|
|
|
|
| 261 |
|
|
|
|
|
|
|
|
|
|
| 262 |
def extract_all_links_from_text(text):
|
| 263 |
accounts = []
|
| 264 |
seen = set()
|
|
|
|
| 265 |
clean_text = re.sub(r'(https?)\s*:\s*//\s*([a-zA-Z0-9.-]+)\s*\.\s*([a-zA-Z]+)', r'\1://\2.\3', text)
|
| 266 |
|
| 267 |
for link in re.findall(r'(https?://[^\s<>"\'\|]+)', clean_text, re.IGNORECASE):
|
|
|
|
| 282 |
if h_m and u_m and p_m:
|
| 283 |
h, u, p = h_m.group(1).rstrip('/'), clean_camouflage(u_m.group(1)), clean_camouflage(p_m.group(1))
|
| 284 |
uid_str = f"x_{h}_{u}"
|
| 285 |
+
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)
|
|
|
|
| 286 |
|
| 287 |
lines = [line.strip() for line in clean_text.split('\n') if line.strip()]
|
|
|
|
| 288 |
current_mac_host = None
|
| 289 |
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)
|
| 290 |
url_patt = re.compile(r'(https?://[^\s]+)')
|
|
|
|
| 292 |
for line in lines:
|
| 293 |
u_m = url_patt.search(line.replace(" ", ""))
|
| 294 |
if u_m and 'username=' not in line: current_mac_host = u_m.group(1).rstrip('/')
|
|
|
|
| 295 |
m_m = mac_patt.search(line)
|
| 296 |
if m_m and current_mac_host:
|
| 297 |
m = m_m.group(1).upper().replace('-', ':')
|
| 298 |
uid_str = f"m_{current_mac_host}_{m}"
|
| 299 |
+
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)
|
|
|
|
| 300 |
|
| 301 |
direct_links = re.findall(r'(https?://[^\s<>"\'\|]+)', clean_text, re.IGNORECASE)
|
| 302 |
for link in direct_links:
|
|
|
|
| 304 |
if link not in seen and 'username=' not in link and not re.search(r'/(?:live|movie|series)/', link) and 'get.php?' not in link:
|
| 305 |
if any(x in link.lower() for x in ['.m3u8', '.ts', '.m3u', 'mpegts', '/stream/', 'amazonaws', '.dev', '.top', 'ok.ru', 'token=', 'index.', '/play/']):
|
| 306 |
uid_str = f"d_{link}"
|
| 307 |
+
if uid_str not in seen: accounts.append({'type': 'direct', 'url': link, '_uid': str(uuid.uuid4())[:8]}); seen.add(uid_str); seen.add(link)
|
|
|
|
| 308 |
return accounts
|
| 309 |
|
| 310 |
# ==========================================
|
| 311 |
+
# 📡 محرك فحص بورتالات الماك الاستثنائي
|
| 312 |
# ==========================================
|
| 313 |
def smart_mac_session(mac, change_ua=False):
|
| 314 |
session = requests.Session()
|
| 315 |
+
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"
|
| 316 |
session.headers.update({"User-Agent": ua, "Cookie": f"mac={mac}; stb_lang=en; timezone=Europe/London;"})
|
| 317 |
retry_strategy = Retry(total=1, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["HEAD", "GET", "OPTIONS"], backoff_factor=0.5)
|
| 318 |
+
session.mount("http://", HTTPAdapter(max_retries=retry_strategy))
|
| 319 |
+
session.mount("https://", HTTPAdapter(max_retries=retry_strategy))
|
|
|
|
| 320 |
return session
|
| 321 |
|
| 322 |
def clean_mac_address(mac): return mac.replace(':', '')
|
|
|
|
| 324 |
def test_mac_portal(host, mac):
|
| 325 |
session = smart_mac_session(mac)
|
| 326 |
conns, is_working, exp = "0 / 1", False, "Unlimited"
|
|
|
|
| 327 |
try:
|
| 328 |
r_hs = session.get(f"{host}/portal.php?type=stb&action=handshake&mac={mac}", timeout=5, verify=False).json()
|
| 329 |
+
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']}"})
|
| 330 |
+
prof_req = session.get(f"{host}/portal.php?type=stb&action=get_profile&mac={mac}", timeout=5, verify=False).json()
|
| 331 |
+
if 'js' in prof_req and prof_req['js'] == []: return False, "Unlimited", "N/A"
|
| 332 |
+
prof = prof_req.get('js', {})
|
| 333 |
+
if prof and isinstance(prof, dict):
|
| 334 |
is_working = True
|
| 335 |
conns = f"{prof.get('active_cons', '0')} / {prof.get('max_connections', '1')}"
|
| 336 |
for key in ['phone', 'expire_billing_date', 'expiry', 'expire', 'end_date', 'expiration', 'expiration_date', 'account_balance']:
|
|
|
|
| 343 |
if exp == "Unlimited":
|
| 344 |
mac_clean = clean_mac_address(mac)
|
| 345 |
endpoints = [
|
| 346 |
+
f"/portal.php?action=get_main_info&type=account_info&mac={mac}&JsHttpRequest=1-xml",
|
| 347 |
+
f"/portal.php?action=get_agreement_info&type=account_info&mac={mac}&JsHttpRequest=1-xml",
|
| 348 |
+
f"/portal.php?action=get_payment_info&type=account_info&mac={mac}&JsHttpRequest=1-xml",
|
| 349 |
f"/stalker_portal/server/api.php?action=get_subscription_info&mac={mac_clean}",
|
| 350 |
f"/stalker_portal/api/v2/player/get_subscription_info?mac={mac_clean}",
|
| 351 |
f"/server/api.php?action=get_subscription_info&mac={mac_clean}"
|
| 352 |
]
|
|
|
|
| 353 |
for ep in endpoints:
|
| 354 |
try:
|
| 355 |
resp = session.get(f"{host.rstrip('/')}{ep}", timeout=5, verify=False)
|
|
|
|
| 359 |
if json_match: raw = json_match.group(1)
|
| 360 |
try:
|
| 361 |
data = json.loads(raw)
|
| 362 |
+
if 'js' in data and data['js'] == []: continue
|
| 363 |
js_data = data.get('js', data) if isinstance(data, dict) else data
|
| 364 |
if isinstance(js_data, dict):
|
| 365 |
+
for key in ['phone', 'expire_billing_date', 'expiry', 'expire', 'end_date', 'expiration', 'expiration_date']:
|
| 366 |
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"]:
|
| 367 |
+
return True, parse_timestamp(str(js_data[key])), conns
|
| 368 |
+
except: pass
|
| 369 |
+
date_match = re.search(r'(\d{4}-\d{2}-\d{2})|([A-Z][a-z]+ \d{1,2}, \d{4})', raw)
|
| 370 |
+
if date_match: return True, parse_timestamp(date_match.group(0)), conns
|
|
|
|
| 371 |
except: continue
|
| 372 |
return True, exp, conns
|
| 373 |
|
|
|
|
| 374 |
def extract_mac_to_m3u_logic(portal, mac, mode="🍿 الكل (لايف+أفلام+مسلسلات)", add_ua=False, ext_choice=".m3u8"):
|
| 375 |
session = smart_mac_session(mac)
|
| 376 |
try:
|
|
|
|
| 378 |
if hs.get('js') and 'token' in hs['js']: session.headers.update({"Authorization": f"Bearer {hs['js']['token']}"})
|
| 379 |
except: pass
|
| 380 |
|
|
|
|
| 381 |
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", [])
|
| 382 |
+
except: return False, "❌ فشل الاتصال بالبورتال.", 0
|
| 383 |
+
if not genres: return False, "❌ لم يتم العثور على تصنيفات.", 0
|
| 384 |
|
| 385 |
m3u_lines = ["#EXTM3U", "# Generated by Shalaby VIP Panel\n"]
|
| 386 |
total_channels = 0
|
| 387 |
ua_suffix = "|User-Agent=IPTVSmartersPro" if add_ua else ""
|
| 388 |
ext_val = "m3u8" if ext_choice == ".m3u8" else "ts" if ext_choice == ".ts" else ""
|
| 389 |
|
|
|
|
| 390 |
strategy = "create_link"
|
| 391 |
first_genre_id = genres[0].get('id') if genres else None
|
| 392 |
if first_genre_id:
|
|
|
|
| 396 |
test_cmd = test_data[0].get("cmd", "") or test_data[0].get("id")
|
| 397 |
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()
|
| 398 |
if test_link.get("js") and test_link["js"].get("id") is None:
|
|
|
|
| 399 |
session = smart_mac_session(mac, change_ua=True)
|
| 400 |
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()
|
| 401 |
if test_link_2.get("js") and test_link_2["js"].get("id") is None: strategy = "raw_fallback"
|
|
|
|
| 421 |
name, cid, cmd_str = ch.get("name", "Unknown"), ch.get("id"), str(ch.get("cmd", ""))
|
| 422 |
if not cid or cid in seen: continue
|
| 423 |
seen.add(cid); added = True
|
|
|
|
| 424 |
if strategy == "create_link": stream_url = f"{portal}/portal.php?type=itv&action=create_link&cmd={urllib.parse.quote(cmd_str)}&mac={mac}"
|
| 425 |
+
else:
|
| 426 |
stream_url = f"{portal}/play/live.php?mac={mac}&stream={cid}"
|
| 427 |
if ext_val: stream_url += f"&extension={ext_val}"
|
|
|
|
| 428 |
logo = ch.get("logo", "")
|
| 429 |
if mode == "⚽ رياضة فقط VIP":
|
| 430 |
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})
|
|
|
|
| 441 |
else: m3u_lines.extend(res_l); total_channels += len(res_l)
|
| 442 |
if mode == "⚽ رياضة فقط VIP" and sports_channels:
|
| 443 |
sports_channels.sort(key=get_vip_sort_key)
|
| 444 |
+
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
|
|
|
|
| 445 |
|
| 446 |
if mode in ["🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"]:
|
| 447 |
try:
|
|
|
|
| 454 |
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", [])
|
| 455 |
if not vod_data: break
|
| 456 |
for vod in vod_data:
|
| 457 |
+
stream_url = f"{portal}/play/vod.php?mac={mac}&stream={vod.get('cmd', '') or vod.get('id')}&extension=mp4"
|
| 458 |
+
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
|
|
|
|
| 459 |
p += 1
|
| 460 |
except: break
|
| 461 |
except: pass
|
|
|
|
| 471 |
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", [])
|
| 472 |
if not ser_data: break
|
| 473 |
for ser in ser_data:
|
| 474 |
+
stream_url = f"{portal}/play/series.php?mac={mac}&stream={ser.get('id')}&extension=mp4"
|
| 475 |
+
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
|
|
|
|
| 476 |
p += 1
|
| 477 |
except: break
|
| 478 |
except: pass
|
| 479 |
|
| 480 |
+
if total_channels == 0: return False, "❌ لم يتم العثور على قنوات محتوى صالحة.", 0
|
| 481 |
return True, "\n".join(m3u_lines), total_channels
|
| 482 |
|
| 483 |
# ==========================================
|
| 484 |
+
# ✅ الفحص الذكي وقناص البنج الفعلي (تخطي الوهمي)
|
| 485 |
# ==========================================
|
| 486 |
def check_server_smart(host, user, pwd):
|
| 487 |
headers = get_headers()
|
|
|
|
| 502 |
except: pass
|
| 503 |
return None
|
| 504 |
|
|
|
|
| 505 |
def test_live_stream_ping(host, user, pw):
|
| 506 |
try:
|
| 507 |
api_url = f"{host.rstrip('/')}/player_api.php?username={user}&password={pw}&action=get_live_streams"
|
| 508 |
r = requests.get(api_url, headers=get_headers(), timeout=5, verify=False).json()
|
| 509 |
if isinstance(r, list) and len(r) > 0:
|
| 510 |
+
test_url = f"{host.rstrip('/')}/live/{user}/{pw}/{r[0].get('stream_id')}.ts"
|
|
|
|
| 511 |
res = requests.get(test_url, headers=get_headers(), stream=True, timeout=5, verify=False)
|
| 512 |
status = res.status_code
|
| 513 |
if status in [200, 302, 206]:
|
| 514 |
chunk = next(res.iter_content(chunk_size=128), b"")
|
| 515 |
res.close()
|
| 516 |
+
if b"<html" in chunk.lower() or b"error" in chunk.lower()[:20]: return False, "❌ السيرفر يعيد صفحة خطأ (البث متوقف)."
|
| 517 |
return True, "✅ يعمل بقوة."
|
| 518 |
res.close()
|
| 519 |
return False, f"❌ متوقف (كود {status})."
|
| 520 |
+
return False, "⚠️ الحساب يعمل لكن قنوات اللايف فارغة."
|
| 521 |
except: return False, "❌ فشل الاتصال."
|
| 522 |
|
| 523 |
def advanced_direct_link_test(url):
|
| 524 |
+
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36'}
|
| 525 |
try:
|
| 526 |
res = requests.get(url, headers=headers, stream=True, timeout=5, verify=False, allow_redirects=True)
|
| 527 |
if res.status_code in [200, 206, 302, 304]:
|
| 528 |
chunk = next(res.iter_content(chunk_size=2048), b"")
|
| 529 |
res.close()
|
| 530 |
text_chunk = chunk.decode('utf-8', errors='ignore').strip()
|
| 531 |
+
if text_chunk.startswith("#EXTM3U") or "#EXTINF" in text_chunk: return True, True, max(1, text_chunk.count("#EXTINF"))
|
| 532 |
+
if "<html" in text_chunk.lower() and not any(x in url for x in ['.dev', '.workers', '.top']): return False, False, 0
|
|
|
|
|
|
|
|
|
|
| 533 |
return True, False, 0
|
|
|
|
| 534 |
except: pass
|
| 535 |
return False, False, 0
|
| 536 |
|
|
|
|
| 544 |
if info:
|
| 545 |
d = calculate_days_left(info.get('exp_date'))
|
| 546 |
ping_ok, _ = test_live_stream_ping(a['host'], a['user'], a['pass'])
|
| 547 |
+
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')})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
return a
|
| 549 |
elif a['type'] == 'mac':
|
| 550 |
prof_ok, exp, conns = test_mac_portal(a['host'], a['mac'])
|
| 551 |
if prof_ok:
|
| 552 |
+
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})
|
| 553 |
+
else: a.update({'days': 'MAC', 'type_auth': 'Portal MAC', 'created_at': '-', 'exp_date': '-', 'status': 'لا يعمل', 'ping_ok': False, 'formats': 'Unknown', 'conns': 'N/A'})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 554 |
return a
|
| 555 |
elif a['type'] == 'direct':
|
| 556 |
ping_ok, is_playlist, ch_count = advanced_direct_link_test(a['url'])
|
| 557 |
if ping_ok:
|
| 558 |
if is_playlist: a.update({'host': 'Playlist M3U', 'days': 'Direct', 'type_auth': 'Playlist', 'status': 'قائمة تشغيل تعمل', 'ping_ok': True, 'formats': 'm3u8/ts', 'ch_count': ch_count})
|
| 559 |
else: a.update({'host': 'Direct Link', 'days': 'Direct', 'type_auth': 'Stream', 'status': 'بث مباشر يعمل', 'ping_ok': True, 'formats': 'ts/m3u8'})
|
| 560 |
+
else: a.update({'host': 'Direct Link', 'days': 'Direct', 'type_auth': 'Stream', 'status': 'لا يعمل', 'ping_ok': False, 'formats': 'Unknown'})
|
|
|
|
| 561 |
return a
|
| 562 |
except: pass
|
| 563 |
return None
|
|
|
|
| 579 |
if result:
|
| 580 |
valid_found.append(result)
|
| 581 |
valid_found.sort(key=live_sort_key, reverse=True)
|
|
|
|
| 582 |
with live_placeholder.container():
|
| 583 |
for srv in valid_found:
|
| 584 |
ping_icon = "🟢" if srv.get('ping_ok') else "🔴"
|
| 585 |
+
days_str = f"{srv.get('days', 0)} يوم" if str(srv.get('days', 0)).isdigit() else str(srv.get('days', 0))
|
|
|
|
| 586 |
if srv.get('type') == 'direct': st.success(f"{ping_icon} {srv['status']} | 🌐 `{srv['url']}` | ⏳ {days_str}")
|
| 587 |
elif srv.get('type') == 'mac': st.success(f"{ping_icon} {srv['status']} | 🌐 `{srv['host']}` | 💳 `{srv['mac']}` | ⏳ {days_str}")
|
| 588 |
else: st.success(f"{ping_icon} {srv['status']} | 🌐 `{srv['host']}` | 👤 `{srv['user']}` | ⏳ {days_str}")
|
| 589 |
+
try:
|
|
|
|
| 590 |
if result.get('type') == 'xtream': save_to_global_vips({'host': result['host'], 'user': result['user'], 'pass': result['pass'], 'days': result.get('days')})
|
| 591 |
elif result.get('type') == 'mac': save_to_global_vips({'host': result['host'], 'mac': result['mac'], 'days': result.get('days'), 'type': 'mac'})
|
| 592 |
except: pass
|
|
|
|
| 603 |
asyncio.set_event_loop(new_loop)
|
| 604 |
result[0] = new_loop.run_until_complete(coro)
|
| 605 |
t = threading.Thread(target=_run)
|
| 606 |
+
t.start(); t.join()
|
|
|
|
| 607 |
return result[0]
|
| 608 |
else: return asyncio.run(coro)
|
| 609 |
|
| 610 |
+
def extract_sports_unified(host, user, pwd, skip_clean=False, ext=".m3u8"):
|
| 611 |
+
domain_parts = host.split('//')[-1].split(':')[0].split('.')
|
| 612 |
+
valid_parts = [p for p in domain_parts if p.lower() not in ['com', 'net', 'org', 'info', 'tv'] and len(p)>2]
|
| 613 |
+
sn = re.sub(r'[^a-zA-Z0-9]', '', valid_parts[0].capitalize() if valid_parts else domain_parts[0].capitalize())
|
| 614 |
+
try:
|
| 615 |
+
r = requests.get(f"{host}/player_api.php?username={user}&password={pwd}", headers=get_headers(), timeout=12, verify=False).json()
|
| 616 |
+
info = r.get('user_info', {})
|
| 617 |
+
if info.get('status') != 'Active': return False, f"❌ متوقف: {sn}"
|
| 618 |
+
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')))
|
| 619 |
+
streams = requests.get(f"{host}/player_api.php?username={user}&password={pwd}&action=get_live_streams", timeout=25, verify=False).json()
|
| 620 |
+
cats = requests.get(f"{host}/player_api.php?username={user}&password={pwd}&action=get_live_categories", timeout=25, verify=False).json()
|
| 621 |
+
cat_map = {i['category_id']: i['category_name'] for i in cats}
|
| 622 |
+
new_chans = []
|
| 623 |
+
for s in streams:
|
| 624 |
+
name, cat = str(s.get('name', '')), str(cat_map.get(s.get('category_id'), "Other"))
|
| 625 |
+
if is_arabic_sport(name, cat):
|
| 626 |
+
ch_id = f"ch_{s.get('stream_id')}_{re.sub(r'\W','',host.lower())}_{re.sub(r'\W','',user.lower())}"
|
| 627 |
+
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"})
|
| 628 |
+
if not new_chans: return False, f"⚠️ لا يوجد باقات رياضية في {sn}"
|
| 629 |
+
db = load_db(); merged = {c['id']: c for c in db}
|
| 630 |
+
for c in new_chans: merged[c['id']] = c
|
| 631 |
+
final = list(merged.values()); final.sort(key=get_vip_sort_key); save_db(final)
|
| 632 |
+
return True, f"✅ تم سحب {len(new_chans)} باقة من [{sn}]"
|
| 633 |
+
except: return False, f"❌ خطأ اتصال في {sn}"
|
| 634 |
+
|
| 635 |
+
def custom_api_extractor(host, user, pw, mode, ext_choice, add_ua):
|
| 636 |
+
output_file = f"Extracted_VIP_{user}.m3u"
|
| 637 |
+
extracted_count = 0
|
| 638 |
+
live_ext = "" if ext_choice == "بدون صيغة" else ext_choice
|
| 639 |
+
ua_suffix = "|User-Agent=IPTVSmartersPro" if add_ua else ""
|
| 640 |
+
host = host.rstrip('/')
|
| 641 |
+
with open(output_file, 'w', encoding='utf-8') as f:
|
| 642 |
+
f.write("#EXTM3U\n")
|
| 643 |
+
if mode == "⚽ رياضة فقط VIP":
|
| 644 |
+
try:
|
| 645 |
+
streams = requests.get(f"{host}/player_api.php?username={user}&password={pw}&action=get_live_streams", timeout=25, verify=False).json()
|
| 646 |
+
cats = requests.get(f"{host}/player_api.php?username={user}&password={pw}&action=get_live_categories", timeout=25, verify=False).json()
|
| 647 |
+
cat_map = {i['category_id']: i['category_name'] for i in cats}
|
| 648 |
+
sn = host.split('//')[-1].split('.')[0].capitalize()
|
| 649 |
+
vip_ch = []
|
| 650 |
+
for s in streams:
|
| 651 |
+
if is_arabic_sport(str(s.get('name','')), str(cat_map.get(s.get('category_id'), ''))):
|
| 652 |
+
vip_ch.append({"name": f"{s.get('name')} [{sn}]", "group": categorize_vip_sport(s.get('name'), sn), "logo": get_vip_logo(s.get('name')) or s.get("stream_icon", ""), "url": f"{host}/live/{user}/{pw}/{s.get('stream_id')}{live_ext}"})
|
| 653 |
+
vip_ch.sort(key=get_vip_sort_key)
|
| 654 |
+
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
|
| 655 |
+
except: pass
|
| 656 |
+
else:
|
| 657 |
+
if mode in ["📺 لايف فقط", "🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"]:
|
| 658 |
+
try:
|
| 659 |
+
data = requests.get(f"{host}/player_api.php?username={user}&password={pw}&action=get_live_streams", timeout=25, verify=False).json()
|
| 660 |
+
for ch in data: f.write(f'#EXTINF:-1 tvg-logo="{ch.get("stream_icon", "")}" group-title="Live TV",{ch.get("name")}\n{host}/live/{user}/{pw}/{ch.get("stream_id")}{live_ext}{ua_suffix}\n'); extracted_count += 1
|
| 661 |
+
except: pass
|
| 662 |
+
if mode in ["🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"]:
|
| 663 |
+
try:
|
| 664 |
+
data = requests.get(f"{host}/player_api.php?username={user}&password={pw}&action=get_vod_streams", timeout=25, verify=False).json()
|
| 665 |
+
for mov in data: f.write(f'#EXTINF:-1 tvg-logo="{mov.get("stream_icon", "")}" group-title="Movies",{mov.get("name")}\n{host}/movie/{user}/{pw}/{mov.get("stream_id")}.{mov.get("container_extension", "mp4")}{ua_suffix}\n'); extracted_count += 1
|
| 666 |
+
except: pass
|
| 667 |
+
if mode == "🍿 الكل (لايف+أفلام+مسلسلات)":
|
| 668 |
+
try:
|
| 669 |
+
data = requests.get(f"{host}/player_api.php?username={user}&password={pw}&action=get_series", timeout=25, verify=False).json()
|
| 670 |
+
for ser in data: f.write(f'#EXTINF:-1 tvg-logo="{ser.get("cover", "")}" group-title="Series",{ser.get("name")}\n{host}/series/{user}/{pw}/{ser.get("series_id")}{live_ext}{ua_suffix}\n'); extracted_count += 1
|
| 671 |
+
except: pass
|
| 672 |
+
return extracted_count > 0, output_file, extracted_count
|
| 673 |
|
| 674 |
# ==========================================
|
| 675 |
+
# 🎨 واجهة الكروت الذكية الفائقة (Super UX Header)
|
| 676 |
# ==========================================
|
| 677 |
def render_server_card(server, idx, context="tab1"):
|
| 678 |
t = server.get('type')
|
|
|
|
| 681 |
uid_str = server.get('_uid', str(uuid.uuid4())[:8])
|
| 682 |
base_k = f"{context}_{idx}_{uid_str}"
|
| 683 |
|
| 684 |
+
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
|
|
|
|
|
|
|
|
|
|
| 685 |
ping_icon = "🟢" if server.get('ping_ok') else "🔴"
|
|
|
|
| 686 |
|
| 687 |
+
if t == 'direct': title = f"{ping_icon} {server['status']} | 🌐 {server.get('url', '')} | ⏳ {days_str}"
|
| 688 |
+
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}"
|
| 689 |
+
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}"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 690 |
|
| 691 |
title += "\u200B" * st.session_state.reset_exp
|
| 692 |
|
| 693 |
with st.expander(title, expanded=force_open):
|
|
|
|
| 694 |
if t == 'xtream':
|
| 695 |
formatted_m3u_url = f"{server['host'].rstrip('/')}/get.php?username={server['user']}&password={server['pass']}&type=m3u_plus"
|
| 696 |
+
st.text_input("🔗 رابط M3U المباشر للمشغل الخارجي (للنسخ السريع):", value=formatted_m3u_url, key=f"link_m3u_{base_k}")
|
| 697 |
st.markdown("---")
|
| 698 |
+
st.markdown(f"### ⚙️ خيارات التحكم والسحب:")
|
|
|
|
| 699 |
elif t == 'direct':
|
| 700 |
+
st.text_input("🔗 رابط الملف التنزيلي السريع:", value=server.get('url', ''), key=f"link_{base_k}")
|
| 701 |
st.markdown("---")
|
| 702 |
+
st.markdown(f"### ⚙️ خيارات التشغيل المعاينة:")
|
|
|
|
|
|
|
|
|
|
| 703 |
|
|
|
|
| 704 |
if t == 'direct':
|
| 705 |
col_p1, col_p2 = st.columns(2)
|
| 706 |
with col_p1:
|
| 707 |
+
if server.get('type_auth') == 'Playlist' and st.button("📥 سحب القنوات (M3U Playlist)", key=f"prep_{base_k}"):
|
| 708 |
+
with st.spinner("جاري سحب المحتوى..."):
|
| 709 |
+
try:
|
| 710 |
+
# 🛡️ تخطي حماية سيرفرات Cloudflare Workers بالتنكر كمتصفح Chrome 🛡️
|
| 711 |
+
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': '*/*'}
|
| 712 |
+
txt = requests.get(server['url'], headers=browser_headers, timeout=15, verify=False).text
|
| 713 |
+
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"
|
| 714 |
+
st.rerun()
|
| 715 |
+
except: st.error("❌ فشل سحب روابط القائمة المباشرة. السيرفر يرفض الاتصال.")
|
|
|
|
| 716 |
with col_p2:
|
| 717 |
+
if st.button("📺 تشغيل معاينة القناة المباشرة", key=f"preview_{base_k}"): st.session_state[f"preview_url_{base_k}"] = server.get('url', ''); st.rerun()
|
|
|
|
|
|
|
| 718 |
|
| 719 |
elif t == 'mac':
|
| 720 |
col_mode, col_ext, col_ua, col_btn = st.columns([3, 2, 2, 2])
|
| 721 |
+
mac_mode = col_mode.selectbox("🎯 قسم المحتوى الباقات:", ["⚽ رياضة فقط VIP", "📺 لايف فقط", "🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"], key=f"mode_{base_k}")
|
| 722 |
+
mac_ext = col_ext.selectbox("صيغة مشغل البث الفعلي:", [".m3u8", ".ts", "بدون صيغة"], index=0, key=f"ext_{base_k}")
|
| 723 |
+
mac_ua = col_ua.checkbox("حماية التخطي UA", value=False, key=f"ua_{base_k}")
|
| 724 |
+
if col_btn.button("📥 بدء سحب الماك", key=f"prep_btn_{base_k}"):
|
| 725 |
+
with st.spinner("جاري الاختراق وسحب القنوات بالتوازي..."):
|
|
|
|
| 726 |
ok, res_text, count = extract_mac_to_m3u_logic(server['host'], server['mac'], mac_mode, mac_ua, mac_ext)
|
| 727 |
+
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"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
else: st.session_state[f"prep_err_{base_k}"] = res_text
|
| 729 |
st.rerun()
|
| 730 |
|
| 731 |
elif t == 'xtream':
|
| 732 |
col_ping, col_preview = st.columns(2)
|
| 733 |
with col_ping:
|
| 734 |
+
if st.button("📡 فحص دقة البث الفعلي (GET البايتات)", key=f"ping_{base_k}"):
|
| 735 |
+
with st.spinner("جاري فحص سلامة داتا الفيديو للبث..."):
|
| 736 |
ok, msg = test_live_stream_ping(server['host'], server['user'], server['pass'])
|
| 737 |
+
st.session_state[f"ping_res_{base_k}"] = (ok, msg); st.rerun()
|
|
|
|
| 738 |
with col_preview:
|
| 739 |
+
if st.button("📺 جلب داتا مشغل المعاينة", key=f"preview_btn_{base_k}"):
|
| 740 |
+
with st.spinner("جاري توليد مسار المشغل..."):
|
| 741 |
try:
|
| 742 |
+
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=5, verify=False).json()
|
| 743 |
+
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"
|
| 744 |
+
else: st.session_state[f"preview_err_{base_k}"] = "لم نجد قنوات للمعاينة الفورية."
|
| 745 |
+
except: st.session_state[f"preview_err_{base_k}"] = "فشل توليد اتصال المعاينة."
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
st.rerun()
|
| 747 |
|
| 748 |
if f"ping_res_{base_k}" in st.session_state:
|
| 749 |
ok, msg = st.session_state[f"ping_res_{base_k}"]
|
| 750 |
if ok: st.success(msg)
|
| 751 |
else: st.error(msg)
|
| 752 |
+
if f"preview_err_{base_k}" in st.session_state: st.error(st.session_state[f"preview_err_{base_k}"])
|
| 753 |
+
|
| 754 |
+
col_m, col_e, col_u, col_b = st.columns([3, 2, 2, 2])
|
| 755 |
+
selected_mode = col_m.selectbox("🎯 المحتوى المستهدف:", ["⚽ رياضة فقط VIP", "📺 لايف فقط", "🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"], key=f"mode_x_{base_k}")
|
| 756 |
+
selected_ext = col_e.selectbox("صيغة البث المخرج:", [".m3u8", ".ts", "بدون صيغة"], key=f"ext_x_{base_k}")
|
| 757 |
+
add_ua = col_u.checkbox("إضافة حماية متقدمة UA", value=False, key=f"ua_x_{base_k}")
|
| 758 |
+
if col_b.button("📥 سحب داتا السيرفر", key=f"prep_btn_x_{base_k}"):
|
| 759 |
+
with st.spinner("جاري تجهيز وتحميل الملف..."):
|
| 760 |
+
success, file, count = custom_api_extractor(server['host'], server['user'], server['pass'], selected_mode, selected_ext, add_ua)
|
| 761 |
+
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']}.m3u"
|
| 762 |
+
else: st.session_state[f"prep_err_{base_k}"] = "❌ السيرفر فارغ أو محظور."
|
| 763 |
+
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 764 |
|
|
|
|
| 765 |
if f"preview_url_{base_k}" in st.session_state:
|
| 766 |
p_url = st.session_state[f"preview_url_{base_k}"]
|
|
|
|
| 767 |
hls_demo = f"https://hls-js.netlify.app/demo/?src={urllib.parse.quote(p_url)}"
|
| 768 |
+
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)
|
| 769 |
+
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)
|
| 770 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 771 |
if f"file_{base_k}" in st.session_state:
|
| 772 |
+
st.success(f"✅ تم السحب بنجاح! ({st.session_state[f'count_{base_k}']} عنصر)")
|
| 773 |
+
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}")
|
| 774 |
+
with st.expander("🔍 فرز ونسخ سريع لقنوات الملف المسحوب مباشرة", expanded=True):
|
|
|
|
| 775 |
raw_text = st.session_state[f"file_{base_k}"].decode('utf-8', errors='ignore')
|
|
|
|
|
|
|
| 776 |
col_s1, col_s2 = st.columns([3, 1])
|
| 777 |
+
search_q = col_s1.text_input("🔍 اكتب كلمة الفرز هنا (مثل bein, ssc):", key=f"sq_{base_k}")
|
| 778 |
+
display_text = raw_text
|
| 779 |
+
if col_s2.button("فلترة 🎯", key=f"sbtn_{base_k}") or search_q:
|
|
|
|
|
|
|
| 780 |
if search_q.strip():
|
| 781 |
+
lines, filtered = raw_text.splitlines(), ["#EXTM3U"]
|
|
|
|
| 782 |
for i in range(len(lines)):
|
| 783 |
+
if lines[i].startswith("#EXTINF") and smart_search_match(search_q, lines[i]):
|
| 784 |
+
filtered.append(lines[i])
|
| 785 |
+
if i + 1 < len(lines): filtered.append(lines[i+1])
|
| 786 |
+
display_text = "\n".join(filtered)
|
| 787 |
+
st.success(f"🎯 تم العثور على {(len(filtered)-1)//2} باقة متطابقة.")
|
| 788 |
+
st.text_area("محتوى الصندوق (تحديد الكل -> نسخ):", value=display_text, height=250, key=f"all_{base_k}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 789 |
|
| 790 |
st.markdown("---")
|
| 791 |
if context == "saved":
|
| 792 |
+
if st.button("🗑️ حذف الحساب من السجل نهائياً", key=f"del_{base_k}"):
|
| 793 |
svs = load_servers(); svs.pop(idx); save_all_servers_to_db(svs)
|
| 794 |
+
st.success("تم الحذف!"); time.sleep(1); st.rerun()
|
| 795 |
else:
|
| 796 |
+
if t == 'xtream' and st.button("💾 حفظ السيرفر في قاعدة بياناتي", key=f"save_{base_k}"):
|
| 797 |
+
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'))
|
| 798 |
+
st.success("✅ تم حفظ السيرفر بنجاح!")
|
| 799 |
+
elif t == 'mac' and st.button("💾 حفظ الماك في قاعدة بياناتي", key=f"save_{base_k}"):
|
| 800 |
+
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'))
|
| 801 |
+
st.success("✅ تم حفظ الماك بنجاح!")
|
|
|
|
|
|
|
| 802 |
|
| 803 |
# ==========================================
|
| 804 |
+
# 🤖 التلغرام والواجهة الرسومية الكاملة
|
| 805 |
# ==========================================
|
| 806 |
@bot.message_handler(commands=['start', 'menu'])
|
| 807 |
def send_menu(message):
|
| 808 |
+
save_admin(message.chat.id); bot.send_message(message.chat.id, "📺 **Shalaby IPTV Tool**\nأهلاً بك يا هندسة في لوحة الإدارة الذكية للتلفزيون!", parse_mode="Markdown")
|
|
|
|
| 809 |
|
| 810 |
@bot.message_handler(commands=['allservers'])
|
| 811 |
def send_all_servers(message):
|
| 812 |
vips = load_global_vips()
|
| 813 |
+
if not vips: bot.reply_to(message, "📭 الصندوق الأسود فارغ حالياً."); return
|
|
|
|
|
|
|
| 814 |
file_path = "All_Hunted_Servers.txt"
|
| 815 |
with open(file_path, "w", encoding="utf-8") as f:
|
| 816 |
+
f.write(f"--- 📦 إجمالي السيرفرات المصادة الشغالة: {len(vips)} ---\n\n")
|
| 817 |
for s in vips:
|
| 818 |
if s.get('type') == 'mac': f.write(f"Portal: {s.get('host')}\nMAC: {s.get('mac')}\nDays Left: {s.get('days')}\n")
|
| 819 |
else: f.write(f"Host: {s.get('host')}\nUser: {s.get('user')}\nPass: {s.get('pass')}\nDays Left: {s.get('days')}\n")
|
| 820 |
f.write("-" * 30 + "\n")
|
| 821 |
+
with open(file_path, "rb") as doc: bot.send_document(message.chat.id, doc, caption=f"🔥 تفضل يا هندسة، ملف إجمالي السيرفرات الصالحة المصادة ({len(vips)}).")
|
| 822 |
|
| 823 |
@bot.message_handler(func=lambda message: True)
|
| 824 |
def handle_all_messages(message):
|
| 825 |
+
accs = extract_all_links_from_text(message.text)
|
|
|
|
| 826 |
if accs:
|
| 827 |
+
msg = bot.reply_to(message, "🔍 جاري فحص وتحليل السيرفرات المرسلة من التلغرام...")
|
| 828 |
valid = []
|
| 829 |
for a in accs:
|
| 830 |
if a['type'] == 'xtream':
|
| 831 |
info = check_server_smart(a['host'], a['user'], a['pass'])
|
| 832 |
+
if info: valid.append(f"✅ `{a['host']}` | 👤 `{a['user']}` | ⏳ {calculate_days_left(info.get('exp_date'))} يوم")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 833 |
elif a['type'] == 'mac':
|
| 834 |
prof_ok, exp, _ = test_mac_portal(a['host'], a['mac'])
|
| 835 |
+
if prof_ok: valid.append(f"✅ `{a['host']}` | 💳 `{a['mac']}` | ⏳ {calculate_days_left(exp)} يوم")
|
| 836 |
+
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")
|
| 837 |
+
else: bot.edit_message_text("❌ عذراً لم تنجح أي روابط مرسلة.", chat_id=msg.chat.id, message_id=msg.message_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 838 |
|
| 839 |
def run_bot_thread_init():
|
| 840 |
for t in threading.enumerate():
|
|
|
|
| 842 |
def start_polling():
|
| 843 |
try: requests.get(f"https://api.telegram.org/bot{TOKEN}/deleteWebhook?drop_pending_updates=True", timeout=5)
|
| 844 |
except: pass
|
| 845 |
+
time.sleep(2); bot.infinity_polling(timeout=20, long_polling_timeout=10, skip_pending=True)
|
| 846 |
+
threading.Thread(target=start_polling, name="TelegramBotThread", daemon=True).start()
|
|
|
|
|
|
|
| 847 |
run_bot_thread_init()
|
| 848 |
|
| 849 |
st.set_page_config(page_title="Shalaby IPTV Tool", page_icon="📺", layout="wide")
|
| 850 |
|
| 851 |
st.markdown("""
|
| 852 |
<style>
|
| 853 |
+
.block-container { max-width: 100% !important; padding: 1rem !important; padding-bottom: 5rem !important; }
|
| 854 |
+
section[data-testid="stSidebar"] { width: 170px !important; min-width: 170px !important; }
|
| 855 |
+
.main { text-align: right; direction: rtl; }
|
| 856 |
+
h1 { font-size: 1.8rem !important; text-align: center; color: #ff4b4b;}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 857 |
div[data-baseweb="tab-list"] { display: flex; flex-direction: column; gap: 8px; }
|
| 858 |
div[data-baseweb="tab"] { width: 100%; justify-content: flex-end; background-color: #2b2b2b; border-radius: 8px; padding: 10px !important; border: 1px solid #444;}
|
| 859 |
div[data-baseweb="tab"]:hover { background-color: #ff4b4b; color: white !important;}
|
| 860 |
.stButton>button { width: 100%; border-radius: 8px; font-weight: bold; background-color: #ff4b4b; color: white; margin-top: 5px;}
|
|
|
|
| 861 |
|
| 862 |
[data-testid="stExpander"] details summary {
|
| 863 |
+
background-color: #262730 !important; border-left: 5px solid #ff9800 !important; border-radius: 8px !important; padding: 12px !important; margin-bottom: 5px !important; box-shadow: 0px 4px 6px rgba(0,0,0,0.3) !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 864 |
}
|
| 865 |
[data-testid="stExpander"] details[open] > div {
|
| 866 |
+
background-color: #1e1e1e !important; border: 1px solid #444 !important; border-radius: 0 0 10px 10px !important; padding: 20px !important; margin-right: 40px !important; width: calc(100% - 40px) !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 867 |
}
|
| 868 |
</style>
|
| 869 |
""", unsafe_allow_html=True)
|
| 870 |
|
| 871 |
+
# نظام الدخول وحفظ الجلسة في المتصفح تلقائياً
|
| 872 |
if not st.session_state.logged_in:
|
| 873 |
+
st.title("🔐 بوابة الدخول التلقائية - Shalaby IPTV Tool")
|
| 874 |
+
tab_in, tab_up = st.tabs(["تسجيل دخول بالحساب الحالي", "إنشاء كود مستخدم جديد"])
|
| 875 |
with tab_up:
|
| 876 |
+
new_user = st.text_input("اختر كود حسابك الجديد:")
|
| 877 |
+
if st.button("🚀 إنشاء وتفعيل التلقائي"):
|
| 878 |
if new_user.strip():
|
| 879 |
users = load_users_registry()
|
| 880 |
if new_user.strip() not in users:
|
| 881 |
users.append(new_user.strip()); save_users_registry(users)
|
| 882 |
+
st.session_state.username, st.session_state.logged_in = new_user.strip(), True
|
| 883 |
st.query_params["user_auth"] = new_user.strip()
|
| 884 |
components.html(f"""<script>localStorage.setItem("user_auth", "{new_user.strip()}");</script>""", height=0, width=0)
|
| 885 |
time.sleep(0.5); st.rerun()
|
| 886 |
+
else: st.error("❌ الكود مستخدم من قبل.")
|
| 887 |
with tab_in:
|
| 888 |
+
exist_user = st.text_input("أدخل كود الدخول الخاص بك:")
|
| 889 |
+
if st.button("🔓 مصادقة ودخول"):
|
| 890 |
if exist_user.strip() in load_users_registry():
|
| 891 |
+
st.session_state.username, st.session_state.logged_in = exist_user.strip(), True
|
| 892 |
st.query_params["user_auth"] = exist_user.strip()
|
| 893 |
components.html(f"""<script>localStorage.setItem("user_auth", "{exist_user.strip()}");</script>""", height=0, width=0)
|
| 894 |
time.sleep(0.5); st.rerun()
|
| 895 |
+
else: st.error("❌ كود الحساب غير مسجل بالمنظومة.")
|
| 896 |
st.stop()
|
| 897 |
|
| 898 |
with st.sidebar:
|
| 899 |
+
st.markdown(f"### 👤 الحساب المتصل:\n`{st.session_state.username}`")
|
|
|
|
| 900 |
if st.button("🚪 تسجيل خروج"):
|
| 901 |
components.html("""<script>localStorage.removeItem("user_auth");</script>""", height=0, width=0)
|
| 902 |
+
st.query_params.clear(); st.session_state.logged_in = False; time.sleep(0.5); st.rerun()
|
| 903 |
|
| 904 |
+
st.title("📺 اللوحة الأسطورية الشاملة - Shalaby VIP Tool")
|
| 905 |
st.markdown("---")
|
| 906 |
|
| 907 |
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
|
| 908 |
+
"🧪 محرك الفحص الصاروخي الذكي", "🗄️ سجل سيرفراتي المحفوظة", "🚀 تجميع وتحديث الماستر داتابيز", "🏭 منقي الباقات للعملاء المخصص", "🔥 فاحص ومحول الماك الفوري", "✨ ساحب القنوات المطور (Playlist Extractor)"
|
| 909 |
])
|
| 910 |
|
| 911 |
with tab1:
|
| 912 |
+
st.markdown("### 🔍 أدخل البيانات أو النصوص المبعثرة للفحص:")
|
| 913 |
if 'valid_found_tab1' in st.session_state and st.session_state['valid_found_tab1']:
|
| 914 |
+
if st.button("🔽 غلق جميع الكروت المفتوحة ومسح الذاكرة التفاعلية", key="close_t1"):
|
| 915 |
st.session_state.reset_exp += 1
|
| 916 |
for k in list(st.session_state.keys()):
|
| 917 |
if k.startswith("file_") or k.startswith("ping_res_") or k.startswith("prep_err_") or k.startswith("preview_"): del st.session_state[k]
|
| 918 |
st.rerun()
|
| 919 |
|
| 920 |
+
bulk_input = st.text_area("ضع الروابط أو نصوص الماكات المبعثرة هنا:", height=150)
|
| 921 |
+
if st.button("🚀 إطلاق موتور فحص Asyncio الشامل"):
|
| 922 |
+
if not bulk_input.strip(): st.warning("⚠️ يرجى تعبئة الحقل بالنصوص أولاً.")
|
| 923 |
else:
|
| 924 |
accs = extract_all_links_from_text(bulk_input)
|
| 925 |
+
if len(accs) == 0: st.error("❌ لم يتم العثور على أ�� روابط أو ماكات صالحة للبناء التقني.")
|
|
|
|
| 926 |
else:
|
| 927 |
+
p_bar, s_msg, l_placeholder = st.progress(0), st.empty(), st.empty()
|
| 928 |
+
v_found = safe_async_run(run_async_scan(accs, p_bar, s_msg, l_placeholder, "tab1"))
|
| 929 |
+
s_msg.success(f"🎉 اكتمل مسح شبكة بايثون بنجاح! تم اصطياد وتحليل {len(v_found)} سيرفر.")
|
| 930 |
+
st.session_state['valid_found_tab1'] = v_found
|
| 931 |
+
if v_found:
|
| 932 |
+
c_db = load_servers()
|
| 933 |
+
for v in v_found:
|
| 934 |
+
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)
|
| 935 |
+
save_all_servers_to_db(c_db)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 936 |
|
| 937 |
if 'valid_found_tab1' in st.session_state and st.session_state['valid_found_tab1']:
|
| 938 |
+
st.markdown("#### ⚙️ الحسابات المصادة والجاهزة للنسخ والسحب الفوري:")
|
| 939 |
for i, acc in enumerate(st.session_state['valid_found_tab1']): render_server_card(acc, i, context="t1")
|
| 940 |
|
| 941 |
with tab2:
|
| 942 |
+
st.markdown("### 🗄️ السيرفرات المسجلة في داتابيز حسابك")
|
| 943 |
svs = load_servers()
|
| 944 |
+
if not svs: st.warning("لا توجد حسابات محفوظة في السجل الخاص بك حالياً.")
|
| 945 |
else:
|
| 946 |
+
if st.button("🔽 تنظيف ذاكرة الكروت المؤقتة بالسجل", key="close_t2"):
|
| 947 |
st.session_state.reset_exp += 1
|
| 948 |
for k in list(st.session_state.keys()):
|
| 949 |
if k.startswith("file_") or k.startswith("ping_res_") or k.startswith("prep_err_") or k.startswith("preview_"): del st.session_state[k]
|
|
|
|
| 951 |
for i, s in enumerate(svs): render_server_card(s, i, context="saved")
|
| 952 |
|
| 953 |
with tab3:
|
| 954 |
+
st.markdown("### 🚀 موتور تحديث الماستر داتابيز الشامل وتجميع الرياضة")
|
| 955 |
+
if st.button("🔄 بدء التجميع والدمج المتوازي لكافة السيرفرات"):
|
| 956 |
svs = load_servers()
|
| 957 |
+
if not svs: st.error("❌ سجل الحسابات فارغ تماماً!")
|
| 958 |
else:
|
| 959 |
+
p_bar3 = st.progress(0)
|
| 960 |
for i, s in enumerate(svs):
|
| 961 |
+
if s.get('type') in ['xtream', 'Xtream API']: extract_sports_unified(s['host'], s['user'], s['pass'], skip_clean=True, ext=".m3u8")
|
| 962 |
+
p_bar3.progress((i + 1) / len(svs))
|
| 963 |
+
f_db = load_db(); m3u_file = export_to_m3u(f_db, "My_Saved_Servers.m3u")
|
| 964 |
+
st.success(f"🎉 تم دمج وفحص وإدراج {len(f_db)} باقة رياضية محدثة بالماستر داتابيز!")
|
| 965 |
+
with open(m3u_file, "rb") as file: st.download_button("💾 تحميل ملف اللستة الموحدة الشغالة لقنواتك", data=file, file_name=f"Master_Shalaby_{st.session_state.username}.m3u")
|
|
|
|
| 966 |
|
| 967 |
with tab4:
|
| 968 |
+
st.markdown("### 🏭 صناعة وتخصيص باقات خاصة للعملاء (Custom Selector)")
|
| 969 |
+
all_ch = load_db()
|
| 970 |
+
if not all_ch: st.warning("⚠️ قاعدة داتابيز القنوات فارغة، يرجى التجميع أولاً من التاب السابق.")
|
| 971 |
else:
|
| 972 |
+
u_groups = set(ch['group'].replace('[SD]','').replace('[HD]','').replace('[FHD]','').replace('[4K]','').strip() for ch in all_ch)
|
| 973 |
+
sel_g = st.multiselect("📌 حدد الباقات المراد تصديرها للعميل:", sorted(list(u_groups)))
|
| 974 |
+
smart_ren = st.checkbox("✨ تنظيف وإعادة هيكلة مسميات الجودة تلقائياً")
|
| 975 |
+
if st.button("🏭 توليد ملف باقة العميل"):
|
| 976 |
+
if not sel_g: st.error("❌ يرجى اختيار باقة واحدة على الأقل للتصدير.")
|
|
|
|
| 977 |
else:
|
| 978 |
+
cust_ch = []
|
| 979 |
+
for ch in all_ch:
|
| 980 |
+
if ch['group'].replace('[SD]','').replace('[HD]','').replace('[FHD]','').replace('[4K]','').strip() in sel_g:
|
| 981 |
+
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")
|
| 982 |
+
cust_ch.append(ch)
|
| 983 |
+
if cust_ch:
|
| 984 |
+
c_file = export_to_m3u(cust_ch, "Client.m3u")
|
| 985 |
+
st.success(f"🎉 تم توليد قنوات الملف المخصص بنجاح بنجاح! ({len(cust_ch)} قناة)")
|
| 986 |
+
with open(c_file, "rb") as file: st.download_button("📥 تحميل ملف العميل الجاهز المخصص", data=file, file_name=f"Client_{st.session_state.username}.m3u")
|
| 987 |
+
else: st.error("❌ لم نجد قنوات متوافقة مع اختيارك.")
|
|
|
|
|
|
|
| 988 |
|
| 989 |
with tab5:
|
| 990 |
+
st.markdown("### 🔥 فاحص الماك المنفرد والمحول المباشر لملف M3U")
|
| 991 |
+
col_h, col_m = st.columns(2)
|
| 992 |
+
m_portal = col_h.text_input("🔗 رابط البورتال (Portal URL):", placeholder="http://domain.xyz:8080")
|
| 993 |
+
m_addr = col_m.text_input("💳 الماك أدرس (MAC Address):", placeholder="00:1A:79:XX:XX:XX")
|
| 994 |
+
col_b1, col_b2, col_b3, col_b4 = st.columns([3, 2, 2, 2])
|
| 995 |
+
standalone_mode = col_b1.selectbox("🎯 فلتر محتوى السحب المطلوب:", ["⚽ رياضة فقط VIP", "📺 لايف فقط", "🎬 لايف + أفلام", "🍿 الكل (لايف+أفلام+مسلسلات)"], key="st_mode")
|
| 996 |
+
standalone_ext = col_b2.selectbox("صيغة المشاهدة المستهدفة:", [".m3u8", ".ts", "بدون صيغة"], key="st_ext")
|
| 997 |
+
standalone_ua = col_b3.checkbox("تفعيل حماية وتخطي UA كـ MAG", value=True)
|
| 998 |
+
if col_b4.button("🚀 فحص وتحويل الماك"):
|
| 999 |
+
if not m_portal or not m_addr: st.warning("⚠️ الرجاء تعبئة بيانات البورتال والماك معاً.")
|
|
|
|
|
|
|
| 1000 |
else:
|
| 1001 |
+
p_clean = "http://" + m_portal.strip().rstrip('/') if not m_portal.strip().startswith("http") else m_portal.strip().rstrip('/')
|
| 1002 |
+
with st.spinner("🕵️♂️ جاري كسر الحماية والتحقق من التواريخ وقنوات السيرفر..."):
|
| 1003 |
+
ok, res_text, count = extract_mac_to_m3u_logic(p_clean, m_addr.strip().upper(), standalone_mode, standalone_ua, standalone_ext)
|
| 1004 |
+
if ok:
|
| 1005 |
+
st.success(f"🎉 نجح التحويل! تم استخراج {count} قناة بكفاءة تامة.")
|
| 1006 |
+
st.download_button("📥 تحميل ملف الـ M3U الناتج فوراً", data=res_text, file_name=f"Converted_{m_addr.replace(':','')}.m3u")
|
| 1007 |
+
else: st.error(res_text)
|
|
|
|
| 1008 |
|
| 1009 |
with tab6:
|
| 1010 |
+
st.markdown("### ✨ ساحب ومحلل محتويات قوائم التشغيل المباشرة (M3U Extractor)")
|
| 1011 |
if 'valid_found_tab6' in st.session_state and st.session_state['valid_found_tab6']:
|
| 1012 |
+
if st.button("🔽 مسح ذاكرة الكروت المؤقتة للتاب", key="close_t6"):
|
| 1013 |
st.session_state.reset_exp += 1
|
| 1014 |
for k in list(st.session_state.keys()):
|
| 1015 |
if k.startswith("file_") or k.startswith("ping_res_") or k.startswith("prep_err_") or k.startswith("preview_"): del st.session_state[k]
|
| 1016 |
st.rerun()
|
| 1017 |
|
| 1018 |
+
ex_input = st.text_area("ضع نصوص قوائم التشغيل أو روابط الـ Workers هنا لفرزها:", height=150)
|
| 1019 |
+
if st.button("⚙️ بدء الفرز والمسح الصاروخي"):
|
| 1020 |
+
if not ex_input.strip(): st.warning("⚠️ يرجى إدخال روابط أولاً للفرز.")
|
| 1021 |
else:
|
| 1022 |
accs = extract_all_links_from_text(ex_input)
|
| 1023 |
+
if not accs: st.error("❌ لم يتم العثور على أي بيانات روابط تشغيل صالحة بالفحص.")
|
| 1024 |
else:
|
| 1025 |
+
p_bar6, s_msg6, l_placeholder6 = st.progress(0), st.empty(), st.empty()
|
| 1026 |
+
v_accs = safe_async_run(run_async_scan(accs, p_bar6, s_msg6, l_placeholder6, "t6"))
|
| 1027 |
+
s_msg6.success(f"🎉 اكتمل المسح بنجاح! تم استخراج {len(v_accs)} كارت تشغيل جاهز.")
|
| 1028 |
+
st.session_state['valid_found_tab6'] = v_accs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1029 |
|
| 1030 |
if 'valid_found_tab6' in st.session_state and st.session_state['valid_found_tab6']:
|
| 1031 |
st.markdown("#### ⚙️ الكروت النهائية بعد الفحص والترتيب التلقائي:")
|
| 1032 |
for i, acc in enumerate(st.session_state['valid_found_tab6']): render_server_card(acc, i, context="t6")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|