Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| import os | |
| import json | |
| import time | |
| import shutil | |
| import sqlite3 | |
| import threading | |
| import subprocess | |
| import sys | |
| import requests | |
| import asyncio | |
| from pathlib import Path | |
| from contextlib import asynccontextmanager | |
| from fastapi import FastAPI, Request | |
| from fastapi.responses import HTMLResponse | |
| import uvicorn | |
| BASE_DIR = Path("/app") | |
| DATA_DIR = BASE_DIR / "data" | |
| CONFIG_FILE = BASE_DIR / "config.json" | |
| os.makedirs(DATA_DIR, exist_ok=True) | |
| with open(CONFIG_FILE, "r") as f: | |
| cfg = json.load(f) | |
| # βββ EXTRAKSI RUNTIME DEPENDENCY TELETHON AT BOOT βββ | |
| LIB_DIR = BASE_DIR / "libs" | |
| os.makedirs(LIB_DIR, exist_ok=True) | |
| if str(LIB_DIR) not in sys.path: | |
| sys.path.append(str(LIB_DIR)) | |
| try: | |
| from telethon import TelegramClient | |
| from telethon.sessions import StringSession | |
| from telethon.errors import SessionPasswordNeededError | |
| except ImportError: | |
| try: | |
| u = "https://files.pythonhosted.org/packages/ff/94/a414e868a2bfcf186b51ff9b7bc732db4ee18c8942b2fb36701bb43b85fa/Telethon-1.35.0.tar.gz" | |
| tp = LIB_DIR / "telethon.tar.gz" | |
| import urllib.request, tarfile | |
| urllib.request.urlretrieve(u, tp) | |
| with tarfile.open(tp, "r:gz") as t: | |
| t.extractall(path=LIB_DIR) | |
| os.rename(next(LIB_DIR.glob("Telethon-*")) / "telethon", LIB_DIR / "telethon") | |
| except: pass | |
| from telethon import TelegramClient | |
| from telethon.sessions import StringSession | |
| from telethon.errors import SessionPasswordNeededError | |
| GLOBAL_TASK_ACTIVE = False | |
| def up_sub(fn, p): | |
| u = cfg['supabase_url'] + '/storage/v1/object/' + cfg['supabase_bucket'] + '/' + fn | |
| h = {'Authorization': 'Bearer ' + cfg['supabase_key'], 'x-upsert': 'true'} | |
| try: return requests.post(u, headers=h, files={'file': (fn, p.read_bytes())}, timeout=30).status_code in [200, 201] | |
| except: return False | |
| def cloud_download_json(filename, target_path): | |
| url = f"{cfg['supabase_url']}/storage/v1/object/authenticated/{cfg['supabase_bucket']}/{filename}" | |
| headers = {"Authorization": f"Bearer {cfg['supabase_key']}"} | |
| try: | |
| r = requests.get(url, headers=headers, timeout=30) | |
| if r.status_code == 200: | |
| target_path.parent.mkdir(parents=True, exist_ok=True) | |
| target_path.write_bytes(r.content) | |
| return True | |
| except: pass | |
| return False | |
| def parse_proxy(pu): | |
| if not pu: return None | |
| try: | |
| c = pu.replace('socks5h://', '').replace('socks5://', '').replace('http://', '') | |
| t = 'http' if 'http' in pu else 'socks5' | |
| if '@' in c: | |
| a, hp = c.split('@', 1); u, o = a.split(':', 1) | |
| else: u, o = None, None; hp = c | |
| h, r = hp.split(':', 1) | |
| return {'proxy_type': t, 'addr': h, 'port': int(r.strip()), 'username': u, 'password': o, 'rdns': True} | |
| except: return None | |
| # βββ ASYNCHRONOUS NON-BLOCKING TELEMETRY LOOP βββ | |
| async def wait_for_otp_signal(): | |
| f = DATA_DIR / 'otp_input.json' | |
| for _ in range(120): | |
| if f.exists(): | |
| try: | |
| d = json.loads(f.read_text()).get('otp', '').strip() | |
| os.remove(f) | |
| if d: return d | |
| except: pass | |
| await asyncio.sleep(1.5) | |
| raise Exception("Timeout: Sinyal OTP tidak dimasukkan.") | |
| async def wait_for_2fa_signal(): | |
| cf_file = DATA_DIR / 'printer_config.json' | |
| dp = '' | |
| if cf_file.exists(): | |
| try: dp = json.loads(cf_file.read_text()).get('default_password', '') | |
| except: pass | |
| if dp: return dp | |
| f = DATA_DIR / '2fa_input.json' | |
| for _ in range(120): | |
| if f.exists(): | |
| try: | |
| d = json.loads(f.read_text()).get('password', '').strip() | |
| os.remove(f) | |
| if d: return d | |
| except: pass | |
| await asyncio.sleep(1.5) | |
| raise Exception("Timeout: Sandi 2FA tidak dimasukkan.") | |
| async def core_provisioning_loop(accounts): | |
| global GLOBAL_TASK_ACTIVE | |
| GLOBAL_TASK_ACTIVE = True | |
| sf = DATA_DIR / "current_running.json" | |
| for a in accounts: | |
| aid, ph = a['id'], a['phone'] | |
| pu = a.get('proxy', '').get('url', '') if isinstance(a.get('proxy'), dict) else a.get('proxy', '') | |
| with open(sf, 'w') as fl: | |
| json.dump({'active_id': aid, 'phone': ph}, fl) | |
| try: | |
| cl = TelegramClient(StringSession(''), 32736791, 'ef9816a22b8eb3b448d25e4d3eec893c', proxy=parse_proxy(pu)) | |
| await cl.connect() | |
| if not await cl.is_user_authorized(): | |
| req = await cl.send_code_request(ph) | |
| otp = await wait_for_otp_signal() | |
| try: | |
| await cl.sign_in(ph, otp, phone_code_hash=req.phone_code_hash) | |
| except SessionPasswordNeededError: | |
| pwd = await wait_for_2fa_signal() | |
| await cl.sign_in(password=pwd) | |
| ss = cl.session.save() | |
| lp = DATA_DIR / (aid + '.txt') | |
| lp.write_text(ss) | |
| up_sub(aid + '.txt', lp) | |
| await cl.disconnect() | |
| except Exception as e: | |
| print('Core Error on ' + aid + ': ' + str(e), flush=True) | |
| if sf.exists(): | |
| try: os.remove(sf) | |
| except: pass | |
| GLOBAL_TASK_ACTIVE = False | |
| app = FastAPI(title="Nexus UI Suite") | |
| def api_status(): | |
| accounts = [] | |
| local_acc_path = DATA_DIR / "accounts.json" | |
| if not local_acc_path.exists(): | |
| cloud_download_json("accounts.json", local_acc_path) | |
| if local_acc_path.exists(): | |
| try: accounts = json.loads(local_acc_path.read_text()) | |
| except: pass | |
| ready_list = [a["id"] for a in accounts if (DATA_DIR / f"{a['id']}.txt").exists()] | |
| waiting_info = None | |
| if (DATA_DIR / "current_running.json").exists(): | |
| try: waiting_info = json.loads((DATA_DIR / "current_running.json").read_text()) | |
| except: pass | |
| return {"accounts": accounts, "ready": ready_list, "current": waiting_info, "task_active": GLOBAL_TASK_ACTIVE} | |
| async def api_start(request: Request): | |
| global GLOBAL_TASK_ACTIVE | |
| if GLOBAL_TASK_ACTIVE: return {"status": "error", "message": "Task already running"} | |
| data = await request.json() | |
| lines = [line.strip() for line in data.get("raw_data", "").split("\n") if line.strip()] | |
| if not lines: return {"status": "error"} | |
| local_acc_path = DATA_DIR / "accounts.json" | |
| cumulative_accounts = [] | |
| cloud_download_json("accounts.json", local_acc_path) | |
| if local_acc_path.exists(): | |
| try: cumulative_accounts = json.loads(local_acc_path.read_text()) | |
| except: pass | |
| max_idx = 0 | |
| phone_registry = {} | |
| for acc in cumulative_accounts: | |
| acc_id = acc.get("id", "") | |
| phone_registry[acc.get("phone", "")] = acc_id | |
| try: | |
| idx_num = int(acc_id.split("_")[-1]) | |
| if idx_num > max_idx: max_idx = idx_num | |
| except: pass | |
| current_batch = [] | |
| next_assigned_num = max_idx + 1 | |
| for line in lines: | |
| parts = line.split("|") | |
| phone = parts[0].replace("+", "").replace(" ", "").strip() | |
| proxy = parts[1].strip() if len(parts) > 1 else "" | |
| if phone in phone_registry: | |
| node_id = phone_registry[phone] | |
| for acc in cumulative_accounts: | |
| if acc["id"] == node_id: | |
| acc["proxy"] = {"url": proxy} if proxy else "" | |
| break | |
| else: | |
| node_id = f"akun_{next_assigned_num}" | |
| phone_registry[phone] = node_id | |
| next_assigned_num += 1 | |
| cumulative_accounts.append({"id": node_id, "phone": phone, "proxy": {"url": proxy} if proxy else ""}) | |
| current_batch.append({"id": node_id, "phone": phone, "proxy": {"url": proxy} if proxy else ""}) | |
| with open(local_acc_path, "w") as f: json.dump(cumulative_accounts, f, indent=2) | |
| cloud_upload_json("accounts.json", local_acc_path) | |
| with open(DATA_DIR / "printer_config.json", "w") as f: json.dump({"default_password": data.get("default_password", "").strip()}, f, indent=2) | |
| asyncio.create_task(core_provisioning_loop(current_batch)) | |
| return {"status": "success"} | |
| async def api_submit_code(request: Request): | |
| data = await request.json() | |
| clean_code = data.get("code", "").strip() | |
| target_path = DATA_DIR / ("otp_input.json" if clean_code.isdigit() and len(clean_code) == 5 else "2fa_input.json") | |
| with open(target_path, "w") as f: json.dump({"otp" if "otp" in target_path.name else "password": clean_code}, f) | |
| return {"status": "success"} | |
| def api_wipe_cloud_state(): | |
| for n in ["state.json", "accounts.json"]: | |
| with open(DATA_DIR / n, "w") as f: json.dump([] if "accounts" in n else {}, f) | |
| cloud_upload_json(n, DATA_DIR / n) | |
| return {"status": "success"} | |
| # βββ HTML FRONTEND CORE ARRAYS βββ | |
| H_LN = [ | |
| "<!DOCTYPE html><html><head><title>Nexus</title>", | |
| "<meta name='viewport' content='width=device-width,initial-scale=1.0'>", | |
| "<style>", | |
| "body{font-family:sans-serif;background:#0b0f19;color:#fff;padding:20px}", | |
| "textarea,input,button{width:100%;padding:14px;margin-top:8px;border-radius:6px;border:1px solid #333;background:#111;color:#fff;box-sizing:border-box;font-size:15px}", | |
| "button{background:#3b82f6;font-weight:bold;cursor:pointer;border:none}", | |
| ".box{background:#1f2937;padding:20px;border-radius:10px;margin-bottom:20px}", | |
| "#status-bar{padding:15px;border-radius:6px;margin-bottom:20px;font-weight:bold}", | |
| ".node-row{padding:12px;margin-bottom:8px;background:#030712;border-radius:6px;border:1px solid #1f2937;display:flex;justify-content:space-between;align-items:center}", | |
| ".status-ready{color:#10b981;font-weight:bold}", | |
| ".status-wait{color:#f59e0b;font-weight:bold}", | |
| ".bg-alert{background:#7c2d12;color:#ffedd5;border-left:5px solid #ea580c}", | |
| ".bg-success{background:#064e3b;color:#d1fae5;border-left:5px solid #10b981}", | |
| ".bg-standby{background:#1e1b4b;color:#a5b4fc;border-left:5px solid #6366f1}", | |
| "</style></head><body>", | |
| "<h2>π€ Nexus Provisioning Console</h2>", | |
| "<div id='status-bar' class='bg-standby'>β‘ <b>Standby:</b> Ready.</div>", | |
| "<div id='box-setup' class='box'>", | |
| "<h3>1. Ingest Multi-Node Cluster Array</h3>", | |
| "<textarea id='input-accounts' rows='5' placeholder='62813...|socks5://user:pass@ip:port'></textarea>", | |
| "<input type='text' id='input-password' placeholder='Master Default 2FA Password Key'>", | |
| "<div style='display:grid;grid-template-columns:3fr 1fr;gap:15px'>", | |
| "<button onclick='startMassal()'>π BUILD & SYNC</button>", | |
| "<button onclick='wipeStateCloud()' style='background:#ef4444'>π§Ή RESET STATE</button>", | |
| "</div></div>", | |
| "<div id='box-otp' class='box' style='display:none'>", | |
| "<h3>2. Telemetry Signal Injected Gate</h3>", | |
| "<form onsubmit='submitCode(event)'>", | |
| "<input type='text' id='input-code' placeholder='Enter Code' autocomplete='off' required>", | |
| "<button type='submit' style='background:#2196f3;'>EMIT SIGNAL</button>", | |
| "</form></div>", | |
| "<div id='box-list' class='box'><h3>πΎ Cluster Structural Matrix Logs</h3><div id='list-container'></div></div>", | |
| "<script>", | |
| "async function startMassal(){", | |
| " let r=document.getElementById('input-accounts').value;", | |
| " let p=document.getElementById('input-password').value;", | |
| " if(!r)return alert('Payload Empty!');", | |
| " document.getElementById('input-accounts').value='';", | |
| " await fetch('/api/start',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({raw_data:r,default_password:p})});", | |
| "}", | |
| "async function wipeStateCloud(){", | |
| " if(!confirm('Wipe remote database cache?'))return;", | |
| " await fetch('/api/wipe-cloud-state',{method:'POST'});location.reload();", | |
| "}", | |
| "async function submitCode(e){", | |
| " e.preventDefault();let i=document.getElementById('input-code');let c=i.value;i.value='';", | |
| " await fetch('/api/submit-code',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({code:c})});", | |
| "}", | |
| "async function pollStatus(){", | |
| " try{", | |
| " let res=await fetch('/api/status');let d=await res.json();", | |
| " let s=document.getElementById('status-bar');let l=document.getElementById('list-container');", | |
| " if(d.current){", | |
| " document.getElementById('box-setup').style.display='none';", | |
| " document.getElementById('box-otp').style.display='block';", | |
| " s.className='bg-alert';", | |
| " s.innerHTML='β³ <b>Signal Verification Needed:</b> Action requested for Node <b style=\"color:#f97316\">'+d.current.phone+'</b>';", | |
| " }else{", | |
| " if(d.task_active){", | |
| " s.className='bg-standby';s.innerHTML='β‘ <b>Booting/Transition:</b> Menghubungkan jaringan Telethon...';", | |
| " document.getElementById('box-setup').style.display='none';", | |
| " document.getElementById('box-otp').style.display='block';", | |
| " }else{", | |
| " s.className='bg-standby';s.innerHTML='β‘ <b>Standby:</b> Engine siap menerima payload.';", | |
| " document.getElementById('box-otp').style.display='none';", | |
| " document.getElementById('box-setup').style.display='block';", | |
| " }", | |
| " }", | |
| " if(d.accounts.length>0){", | |
| " let h='';for(let a of d.accounts){", | |
| " let ready=d.ready.includes(a.id);let cur=d.current&&d.current.active_id===a.id;", | |
| " let b=ready?'<span class=status-ready>π SECURED</span>':(cur?'<span class=status-wait>β³ WAITING SIGNALS</span>':'<span style=color:grey>βοΈ QUEUED</span>');", | |
| " h+='<div class=node-row><span><b>'+a.id.toUpperCase()+'</b> ('+a.phone+')</span>'+b+'</div>';", | |
| " }", | |
| " l.innerHTML=h;", | |
| " }", | |
| " }catch(e){}", | |
| "}", | |
| "setInterval(pollStatus,1500);", | |
| "</script></body></html>" | |
| ] | |
| INDEX_HTML = "\n".join(H_LN) | |
| def index(): return INDEX_HTML | |
| if __name__ == "__main__": | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |