import httpx from bs4 import BeautifulSoup import re from datetime import datetime, timedelta, timezone import time from flask import Flask, Response, request, redirect from threading import Thread from collections import deque import json from queue import Queue import os import uuid import sys # Force print to flush immediately print = lambda *args, **kwargs: __builtins__.print(*args, **kwargs, flush=True) BASE = "http://159.69.3.189" LOGIN_URL = f"{BASE}/login" GET_RANGE_URL = f"{BASE}/portal/sms/received/getsms" GET_NUMBER_URL = f"{BASE}/portal/sms/received/getsms/number" GET_SMS_URL = f"{BASE}/portal/sms/received/getsms/number/sms" TELEGRAM_PROXY_URL = "https://danihitambangetjir.termai.cc/api/proxy" CUSTOM_DOMAIN = "https://fourstore-otp.hf.space" ACCOUNTS_FILE = "accounts.json" # Load accounts dari file def load_accounts(): if os.path.exists(ACCOUNTS_FILE): try: with open(ACCOUNTS_FILE, 'r') as f: return json.load(f) except Exception as e: print(f"โŒ Error load accounts: {e}") return {} return {} def save_accounts(accounts): try: with open(ACCOUNTS_FILE, 'w') as f: json.dump(accounts, f, indent=2) print(f"๐Ÿ’พ Accounts saved to {ACCOUNTS_FILE}") except Exception as e: print(f"โŒ Error save accounts: {e}") # Inisialisasi accounts accounts = load_accounts() print(f"\n๐Ÿ“Š Loaded {len(accounts)} accounts from file") # Tambahkan runtime data ke setiap account for acc_id in accounts: accounts[acc_id]["session"] = None accounts[acc_id]["csrf"] = None accounts[acc_id]["status"] = False accounts[acc_id]["otp_logs"] = [] accounts[acc_id]["sent_cache"] = [] accounts[acc_id]["sms_cache"] = {} accounts[acc_id]["sms_counter"] = {} accounts[acc_id]["range_counter"] = {} accounts[acc_id]["last_cleanup"] = time.time() print(f" โœ… Account {acc_id}: {accounts[acc_id].get('username')}") app = Flask('') app.secret_key = "fourstore-multi-account-secret" sse_clients = [] def get_wib_time(): return datetime.now(timezone.utc) + timedelta(hours=7) def get_search_date(): wib = get_wib_time() return (wib - timedelta(days=1)).strftime("%Y-%m-%d") if wib.hour < 7 else wib.strftime("%Y-%m-%d") def login_account(account_id, username, password, bot_token, chat_id): try: print(f"\n{'='*60}") print(f"๐Ÿ” PROSES LOGIN UNTUK: {username} (ID: {account_id})") print(f"{'='*60}") print(f"๐Ÿ“ค Membuat session baru...") session = httpx.Client(follow_redirects=True, timeout=30.0) print(f"๐Ÿ“ฅ GET login page: {LOGIN_URL}") r = session.get(LOGIN_URL, timeout=30) print(f"๐Ÿ“Š Status code: {r.status_code}") if r.status_code != 200: print(f"โŒ Gagal load login page") return False, f"HTTP {r.status_code}" soup = BeautifulSoup(r.text, "html.parser") token = soup.find("input", {"name": "_token"}) if not token: print(f"โŒ Token CSRF tidak ditemukan dalam HTML") print(f"๐Ÿ“„ Preview HTML: {r.text[:200]}") return False, "Token tidak ditemukan" csrf_token = token.get("value") print(f"โœ… CSRF Token: {csrf_token[:20]}...") print(f"๐Ÿ“ค POST login data...") r = session.post(LOGIN_URL, data={ "_token": csrf_token, "email": username, "password": password }, timeout=30) print(f"๐Ÿ“Š Response status: {r.status_code}") if "dashboard" in r.text.lower() or "logout" in r.text.lower(): print(f"โœ…โœ…โœ… LOGIN BERHASIL! โœ…โœ…โœ…") print(f"๐Ÿ“ฆ Menyimpan session untuk {username}") accounts[account_id]["session"] = session accounts[account_id]["csrf"] = csrf_token accounts[account_id]["status"] = True accounts[account_id]["username"] = username accounts[account_id]["password"] = password accounts[account_id]["bot_token"] = bot_token accounts[account_id]["chat_id"] = chat_id accounts[account_id]["last_login"] = time.time() save_accounts(accounts) return True, "Login berhasil" else: print(f"โŒโŒโŒ LOGIN GAGAL! โŒโŒโŒ") print(f"๐Ÿ“„ Response preview: {r.text[:300]}") return False, "Login gagal - cek username/password" except Exception as e: print(f"โŒ EXCEPTION: {str(e)}") return False, str(e) def tg_send(account_id, msg): try: account = accounts.get(account_id) if not account or not account.get("bot_token") or not account.get("chat_id"): return False url = f"{TELEGRAM_PROXY_URL}?url=https://api.telegram.org/bot{account['bot_token']}/sendMessage" payload = { "chat_id": account['chat_id'], "text": msg, "parse_mode": "Markdown" } httpx.post(url, json=payload, timeout=30) return True except: return False def clean_country(rng): return re.sub(r"\s*\d+$", "", rng).strip() if rng else "UNKNOWN" def mask_number(number): if not number: return "UNKNOWN" clean = re.sub(r"[^\d+]", "", number) if len(clean) <= 6: return clean return f"{clean[:4]}****{clean[-3:]}" def map_service(raw): if not raw: return "UNKNOWN" s = raw.lower().strip() if 'whatsapp' in s: return "WHATSAPP" if 'telegram' in s: return "TELEGRAM" if 'google' in s or 'gmail' in s: return "GOOGLE" if 'facebook' in s or 'fb' in s: return "FACEBOOK" if 'instagram' in s or 'ig' in s: return "INSTAGRAM" if 'tiktok' in s: return "TIKTOK" if 'temu' in s: return "TEMU" return raw.upper() def extract_otp(text): if not text: return None m = re.search(r"\b(\d{3}[- ]?\d{3})\b", text) if m: return m.group(0).replace("-", "").replace(" ", "") m = re.search(r"\b(\d{4,6})\b", text) if m: return m.group(0) digits = re.findall(r'\d+', text) for d in digits: if 4 <= len(d) <= 6: return d return None def get_ranges_with_count(account_id): account = accounts.get(account_id) if not account or not account.get("session") or not account.get("csrf"): return [] try: date = get_search_date() r = account["session"].post(GET_RANGE_URL, data={ "_token": account["csrf"], "from": date, "to": date }, timeout=15) soup = BeautifulSoup(r.text, "html.parser") ranges_data = [] for item in soup.select(".item"): name_div = item.select_one(".col-sm-4") if not name_div: continue rng = name_div.get_text(strip=True) count_p = item.select_one(".col-3 .mb-0.pb-0") count = int(count_p.get_text(strip=True)) if count_p else 0 ranges_data.append({ "name": rng, "count": count }) return ranges_data except: return [] def get_numbers_with_count(account_id, rng): account = accounts.get(account_id) if not account or not account.get("session") or not account.get("csrf"): return [] try: date = get_search_date() r = account["session"].post(GET_NUMBER_URL, data={ "_token": account["csrf"], "start": date, "end": date, "range": rng }, timeout=15) soup = BeautifulSoup(r.text, "html.parser") numbers_data = [] for div in soup.find_all("div", onclick=True): onclick = div.get("onclick", "") match = re.search(r"getDetialsNumber\w*\('?(\d+)'?", onclick) if not match: match = re.search(r"open_(\d+)", onclick) if not match: match = re.search(r"'(\d+)'", onclick) if match: num = match.group(1) if num and len(num) > 5: parent = div.find_parent("div", class_="card") count = 0 if parent: p_tag = parent.find("p", class_="mb-0 pb-0") if p_tag: try: count = int(p_tag.get_text(strip=True)) except: count = 0 numbers_data.append({ "number": num, "count": count }) if len(numbers_data) == 0: for div in soup.find_all("div", class_="col-sm-4"): text = div.get_text(strip=True) match = re.search(r'\b(\d{10,15})\b', text) if match: num = match.group(1) parent = div.find_parent("div", class_="card") count = 0 if parent: p_tag = parent.find("p", class_="mb-0 pb-0") if p_tag: try: count = int(p_tag.get_text(strip=True)) except: count = 0 numbers_data.append({ "number": num, "count": count }) return numbers_data except: return [] def get_sms_fast(account_id, rng, number): account = accounts.get(account_id) if not account or not account.get("session") or not account.get("csrf"): return [] try: date = get_search_date() cache_key = f"{rng}-{number}" if cache_key in account["sms_cache"]: timestamp, results = account["sms_cache"][cache_key] if time.time() - timestamp < 5: return results r = account["session"].post(GET_SMS_URL, data={ "_token": account["csrf"], "start": date, "end": date, "Number": number, "Range": rng }, timeout=20) soup = BeautifulSoup(r.text, "html.parser") results = [] for card in soup.select("div.card.card-body"): try: service = "UNKNOWN" service_div = card.select_one("div.col-sm-4") if service_div: raw = service_div.get_text(strip=True) service = map_service(raw) msg_p = card.find("p", class_="mb-0 pb-0") if msg_p: sms = msg_p.get_text(strip=True) otp = extract_otp(sms) if otp: results.append((service, sms, otp)) except: continue account["sms_cache"][cache_key] = (time.time(), results) return results except: return [] def add_otp_log(account_id, country, number, service, otp, sms): account = accounts.get(account_id) if not account: return wib = get_wib_time() log_entry = { "time": wib.strftime("%H:%M:%S"), "country": country, "number": number, "service": service, "otp": otp, "sms": sms[:80] + "..." if len(sms) > 80 else sms, "account_id": account_id, "account_username": account.get("username", "Unknown") } if "otp_logs" not in account: account["otp_logs"] = [] account["otp_logs"].insert(0, log_entry) if len(account["otp_logs"]) > 100: account["otp_logs"] = account["otp_logs"][:100] broadcast_sse(log_entry) save_accounts(accounts) return log_entry def broadcast_sse(data): msg = f"data: {json.dumps(data)}\n\n" dead = [] for q in sse_clients: try: q.put(msg) except: dead.append(q) for q in dead: sse_clients.remove(q) @app.route('/') def home(): # Gabungkan semua log dari semua akun all_logs = [] for acc_id, acc in accounts.items(): if acc.get("otp_logs"): for log in acc["otp_logs"]: log_copy = log.copy() log_copy["account_username"] = acc.get("username", "Unknown") all_logs.append(log_copy) all_logs = sorted(all_logs, key=lambda x: x.get("time", ""), reverse=True) search_query = request.args.get('q', '').lower() filter_service = request.args.get('service', '') filter_account = request.args.get('account', '') if search_query: all_logs = [log for log in all_logs if search_query in log.get('country', '').lower() or search_query in log.get('number', '').lower() or search_query in log.get('otp', '').lower() or search_query in log.get('sms', '').lower()] if filter_service: all_logs = [log for log in all_logs if log.get('service') == filter_service] if filter_account: all_logs = [log for log in all_logs if log.get('account_id') == filter_account] all_services = list(set([log.get('service') for log in all_logs if log.get('service')])) # Hitung total OTP dari semua akun total_otp = sum(len(acc.get('sent_cache', [])) for acc in accounts.values()) today_otp = len(all_logs) # Daftar akun untuk filter accounts_list = [] for acc_id, acc in accounts.items(): accounts_list.append({ "id": acc_id, "username": acc.get("username", "Unknown"), "status": acc.get("status", False) }) html = f""" OTP MULTI ACCOUNT ยท FOURSTORE

OTP MULTI ACCOUNT ยท FOURSTORE

{CUSTOM_DOMAIN}

โ— ONLINE
Total Akun
{len(accounts)}
Akun Online
{sum(1 for a in accounts.values() if a.get('status'))}
Total OTP
{total_otp}
WIB
{get_wib_time().strftime('%H:%M:%S')}

โž• Tambah Akun Baru

{generate_account_cards(accounts_list)}
โœ• Reset ๐Ÿ“Š {len(all_logs)} hasil

๐Ÿ“จ OTP TERBARU LIVE

{generate_otp_rows(all_logs, search_query)}
WIB Akun Country Number Service OTP Message
""" return html def generate_account_cards(accounts_list): if not accounts_list: return '
Belum ada akun. Tambah akun di atas!
' html = "" for acc in accounts_list: status_class = "online" if acc["status"] else "offline" html += f"""

{acc['username'][:20]}{'...' if len(acc['username']) > 20 else ''}

Status: {'๐ŸŸข Online' if acc['status'] else '๐Ÿ”ด Offline'}

Hapus
""" return html def generate_otp_rows(logs, search_query): if not logs: return '๐Ÿ” Belum ada OTP' rows = "" for log in logs[:100]: country = log.get('country', '') number = log.get('number', '') otp = log.get('otp', '') sms = log.get('sms', '') service = log.get('service', 'UNKNOWN') account = log.get('account_username', 'Unknown')[:15] if search_query: country = re.sub(f'({re.escape(search_query)})', r'\1', country, flags=re.I) number = re.sub(f'({re.escape(search_query)})', r'\1', number, flags=re.I) otp = re.sub(f'({re.escape(search_query)})', r'\1', otp, flags=re.I) service_class = service.lower().replace(' ', '') rows += f''' {log.get('time', '')} {account} {country} {number} {service} {otp}
{log.get('sms', '')}
''' return rows @app.route('/add_account', methods=['POST']) def add_account_route(): account_id = str(uuid.uuid4())[:8] username = request.form['username'] password = request.form['password'] bot_token = request.form.get('bot_token', '') chat_id = request.form.get('chat_id', '') print(f"\nโž• TAMBAH AKUN BARU: {username} (ID: {account_id})") accounts[account_id] = { "id": account_id, "username": username, "password": password, "bot_token": bot_token, "chat_id": chat_id, "session": None, "csrf": None, "status": False, "otp_logs": [], "sent_cache": [], "sms_cache": {}, "sms_counter": {}, "range_counter": {}, "last_cleanup": time.time(), "created_at": time.time() } save_accounts(accounts) print(f"โœ… Akun ditambahkan: {username}") return redirect('/') @app.route('/delete_account/') def delete_account_route(account_id): if account_id in accounts: username = accounts[account_id].get('username', 'Unknown') print(f"\n๐Ÿ—‘๏ธ HAPUS AKUN: {username} (ID: {account_id})") del accounts[account_id] save_accounts(accounts) print(f"โœ… Akun dihapus") return redirect('/') @app.route('/login_account/', methods=['POST']) def login_account_route(account_id): print(f"\n๐Ÿ””๐Ÿ””๐Ÿ”” LOGIN ACCOUNT DIPANGGIL: {account_id} ๐Ÿ””๐Ÿ””๐Ÿ””") if account_id in accounts: acc = accounts[account_id] print(f"๐Ÿ“‹ Data akun: {acc.get('username')}") print(f"๐Ÿ” Mencoba login...") success, msg = login_account( account_id, acc['username'], acc['password'], acc.get('bot_token', ''), acc.get('chat_id', '') ) print(f"๐Ÿ“ Result: {success} - {msg}") if success: print(f"โœ…โœ…โœ… LOGIN BERHASIL! Memulai thread scraper...") thread = Thread(target=run_account_scraper, args=(account_id,), daemon=True) thread.start() print(f"โœ… Thread scraper dimulai untuk {acc['username']}") else: print(f"โŒโŒโŒ LOGIN GAGAL: {msg}") else: print(f"โŒ Account ID {account_id} tidak ditemukan!") return redirect('/') @app.route('/logout_account/', methods=['POST']) def logout_account_route(account_id): if account_id in accounts: username = accounts[account_id].get('username', 'Unknown') print(f"\n๐Ÿ”Œ LOGOUT: {username} (ID: {account_id})") accounts[account_id]["status"] = False accounts[account_id]["session"] = None accounts[account_id]["csrf"] = None save_accounts(accounts) print(f"โœ… Logout berhasil") return redirect('/') @app.route('/stream') def stream(): def generate(): q = Queue() sse_clients.append(q) try: while True: yield q.get() except: if q in sse_clients: sse_clients.remove(q) return Response(generate(), mimetype="text/event-stream") def run_account_scraper(account_id): account = accounts.get(account_id) if not account: return username = account['username'] print(f"\n๐Ÿš€๐Ÿš€๐Ÿš€ STARTING SCRAPER FOR: {username} ๐Ÿš€๐Ÿš€๐Ÿš€") loop_count = 0 while account.get("status"): loop_count += 1 try: print(f"\n{'='*60}") print(f"๐Ÿ”„ [{username}] LOOP #{loop_count}") print(f"{'='*60}") if time.time() - account.get("last_cleanup", 0) > 300: account["sms_cache"] = {} account["sms_counter"] = {} account["range_counter"] = {} account["last_cleanup"] = time.time() print(f"๐Ÿงน [{username}] Cache cleared") ranges_data = get_ranges_with_count(account_id) print(f"๐Ÿ“Š [{username}] Total ranges: {len(ranges_data)}") for range_item in ranges_data: if not account.get("status"): break rng = range_item["name"] current_count = range_item["count"] prev_count = account["range_counter"].get(rng, 0) if current_count > prev_count: country = clean_country(rng) print(f"\n๐Ÿ”ฅ RANGE BERUBAH: {country} ({username})") print(f" ๐Ÿ“Š {prev_count} โ†’ {current_count} SMS") account["range_counter"][rng] = current_count numbers_data = get_numbers_with_count(account_id, rng) print(f" ๐Ÿ“ž Total nomor: {len(numbers_data)}") for number_item in numbers_data: if not account.get("status"): break num = number_item["number"] num_count = number_item["count"] key = f"{rng}-{num}" prev_num_count = account["sms_counter"].get(key, 0) if num_count > prev_num_count: print(f" ๐Ÿ“ฑ Nomor: {mask_number(num)}") print(f" ๐Ÿ“จ {prev_num_count} โ†’ {num_count} SMS") account["sms_counter"][key] = num_count all_sms = get_sms_fast(account_id, rng, num) new_sms = all_sms[prev_num_count:] for service, sms, otp in new_sms: if otp: sms_id = f"{rng}-{num}-{otp}" if sms_id not in account["sent_cache"]: masked = mask_number(num) msg = f"๐Ÿ”” *NEW OTP*\n๐ŸŒ {country}\n๐Ÿ“ž `{masked}`\n๐Ÿ’ฌ {service}\n๐Ÿ” `{otp}`\n\n{sms[:300]}" print(f" ๐Ÿ“ค Mengirim OTP {otp} ke Telegram...") if tg_send(account_id, msg): account["sent_cache"].append(sms_id) if len(account["sent_cache"]) > 1000: account["sent_cache"] = account["sent_cache"][-1000:] add_otp_log(account_id, country, masked, service, otp, sms) print(f" โœ… OTP: {otp} - {service} TERKIRIM!") time.sleep(0.5) else: print(f" โญ๏ธ Range {clean_country(rng)} tidak berubah (count: {current_count})") print(f"\nโณ [{username}] Tidur 2 detik...") time.sleep(2) except Exception as e: print(f"โŒ ERROR in scraper for {username}: {str(e)}") time.sleep(5) print(f"\n๐Ÿ›‘๐Ÿ›‘๐Ÿ›‘ SCRAPER STOPPED FOR: {username} ๐Ÿ›‘๐Ÿ›‘๐Ÿ›‘") def run_server(): app.run(host='0.0.0.0', port=7860, debug=False, threaded=True) # Start server thread Thread(target=run_server, daemon=True).start() def main(): print("\n" + "="*60) print(" ๐Ÿ”ฅ OTP MULTI ACCOUNT - FOURSTORE") print(" โšก PORT: 7860") print(f" ๐ŸŒ DOMAIN: {CUSTOM_DOMAIN}") print(" ๐Ÿ“ Data tersimpan di accounts.json") print(" ๐Ÿ“‹ LOGGING: FULL DETAIL (FLUSH ENABLED)") print("="*60 + "\n") # Login otomatis untuk akun yang sudah login sebelumnya for acc_id, acc in accounts.items(): if acc.get("status"): print(f"๐Ÿ”„ Auto-login untuk {acc['username']}...") success, msg = login_account( acc_id, acc['username'], acc['password'], acc.get('bot_token', ''), acc.get('chat_id', '') ) if success: thread = Thread(target=run_account_scraper, args=(acc_id,), daemon=True) thread.start() print(f"โœ… {acc['username']} online") else: print(f"โŒ {acc['username']} offline: {msg}") acc["status"] = False save_accounts(accounts) print("\nโœ… BOT SIAP! Dashboard: https://fourstore-otp.hf.space") print("๐Ÿ“‹ Log akan muncul di terminal ini setiap ada aktivitas\n") # Keep main thread alive while True: time.sleep(60) if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n๐Ÿ›‘ BOT STOPPED") save_accounts(accounts)