Fourstore commited on
Commit
d1e981b
Β·
verified Β·
1 Parent(s): a114d8b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +136 -140
app.py CHANGED
@@ -18,9 +18,10 @@ GET_NUMBER_URL = f"{BASE}/portal/sms/received/getsms/number"
18
  GET_SMS_URL = f"{BASE}/portal/sms/received/getsms/number/sms"
19
 
20
  TELEGRAM_PROXY_URL = "https://danihitambangetjir.termai.cc/api/proxy"
21
- CUSTOM_DOMAIN = "https://fourstore-tele.hf.space"
22
  ACCOUNTS_FILE = "accounts.json"
23
 
 
24
  def load_accounts():
25
  if os.path.exists(ACCOUNTS_FILE):
26
  try:
@@ -34,7 +35,10 @@ def save_accounts(accounts):
34
  with open(ACCOUNTS_FILE, 'w') as f:
35
  json.dump(accounts, f, indent=2)
36
 
 
37
  accounts = load_accounts()
 
 
38
  for acc_id in accounts:
39
  accounts[acc_id]["session"] = None
40
  accounts[acc_id]["csrf"] = None
@@ -47,7 +51,7 @@ for acc_id in accounts:
47
  accounts[acc_id]["last_cleanup"] = time.time()
48
 
49
  app = Flask('')
50
- app.secret_key = "fourstore-secret-key-123"
51
  sse_clients = []
52
 
53
  def get_wib_time():
@@ -57,63 +61,44 @@ def get_search_date():
57
  wib = get_wib_time()
58
  return (wib - timedelta(days=1)).strftime("%Y-%m-%d") if wib.hour < 7 else wib.strftime("%Y-%m-%d")
59
 
60
- def login_account(account_id, username, password):
61
  try:
62
- print(f"\nπŸ” LOGIN ACCOUNT: {username}")
63
- print(f"πŸ“€ Membuat session baru...")
64
  session = httpx.Client(follow_redirects=True, timeout=30.0)
65
 
66
- print(f"πŸ“₯ GET login page: {LOGIN_URL}")
67
  r = session.get(LOGIN_URL, timeout=30)
68
- print(f"πŸ“Š Status code: {r.status_code}")
69
-
70
- if r.status_code != 200:
71
- print(f"❌ Gagal load login page")
72
- return False, f"HTTP {r.status_code}"
73
-
74
  soup = BeautifulSoup(r.text, "html.parser")
75
  token = soup.find("input", {"name": "_token"})
76
  if not token:
77
- print(f"❌ Token CSRF tidak ditemukan dalam HTML")
78
- print(f"πŸ“„ Preview HTML: {r.text[:200]}")
79
  return False, "Token tidak ditemukan"
80
 
81
  csrf_token = token.get("value")
82
- print(f"βœ… CSRF Token: {csrf_token[:20]}...")
83
-
84
- print(f"πŸ“€ POST login data...")
85
  r = session.post(LOGIN_URL, data={
86
  "_token": csrf_token,
87
  "email": username,
88
  "password": password
89
  }, timeout=30)
90
- print(f"πŸ“Š Response status: {r.status_code}")
91
-
92
  if "dashboard" in r.text.lower() or "logout" in r.text.lower():
93
- print(f"βœ…βœ…βœ… LOGIN BERHASIL! βœ…βœ…βœ…")
94
- print(f"πŸ“¦ Session aktif, menyimpan data...")
95
-
96
  accounts[account_id]["session"] = session
97
  accounts[account_id]["csrf"] = csrf_token
98
  accounts[account_id]["status"] = True
 
 
 
 
99
  accounts[account_id]["last_login"] = time.time()
100
  save_accounts(accounts)
101
-
102
  return True, "Login berhasil"
103
  else:
104
- print(f"❌❌❌ LOGIN GAGAL! ❌❌❌")
105
- print(f"πŸ“„ Response preview: {r.text[:300]}")
106
- return False, "Login gagal - cek username/password"
107
-
108
  except Exception as e:
109
- print(f"❌ EXCEPTION: {str(e)}")
110
  return False, str(e)
111
 
112
  def tg_send(account_id, msg):
113
  try:
114
  account = accounts.get(account_id)
115
  if not account or not account.get("bot_token") or not account.get("chat_id"):
116
- print(f"⚠️ Telegram: No bot_token/chat_id for {account_id}")
117
  return False
118
 
119
  url = f"{TELEGRAM_PROXY_URL}?url=https://api.telegram.org/bot{account['bot_token']}/sendMessage"
@@ -122,12 +107,9 @@ def tg_send(account_id, msg):
122
  "text": msg,
123
  "parse_mode": "Markdown"
124
  }
125
- print(f"πŸ“€ Send to Telegram: {msg[:50]}...")
126
- r = httpx.post(url, json=payload, timeout=30)
127
- print(f"πŸ“Š Telegram response: {r.status_code}")
128
  return True
129
- except Exception as e:
130
- print(f"❌ Telegram error: {e}")
131
  return False
132
 
133
  def clean_country(rng):
@@ -168,21 +150,16 @@ def extract_otp(text):
168
  def get_ranges_with_count(account_id):
169
  account = accounts.get(account_id)
170
  if not account or not account.get("session") or not account.get("csrf"):
171
- print(f"⚠️ Account {account_id} invalid")
172
  return []
173
 
174
  try:
175
  date = get_search_date()
176
- print(f"\nπŸ” AMBIL RANGE - {account['username']} - Tanggal: {date}")
177
-
178
  r = account["session"].post(GET_RANGE_URL, data={
179
  "_token": account["csrf"],
180
  "from": date,
181
  "to": date
182
  }, timeout=15)
183
-
184
- print(f"πŸ“Š Status: {r.status_code}")
185
-
186
  soup = BeautifulSoup(r.text, "html.parser")
187
  ranges_data = []
188
 
@@ -198,11 +175,9 @@ def get_ranges_with_count(account_id):
198
  "name": rng,
199
  "count": count
200
  })
201
- print(f" βœ… {rng} - {count} SMS")
202
 
203
  return ranges_data
204
- except Exception as e:
205
- print(f"❌ Error get_ranges: {e}")
206
  return []
207
 
208
  def get_numbers_with_count(account_id, rng):
@@ -212,8 +187,6 @@ def get_numbers_with_count(account_id, rng):
212
 
213
  try:
214
  date = get_search_date()
215
- print(f"\n πŸ“ž AMBIL NOMOR: {rng}")
216
-
217
  r = account["session"].post(GET_NUMBER_URL, data={
218
  "_token": account["csrf"],
219
  "start": date,
@@ -249,11 +222,30 @@ def get_numbers_with_count(account_id, rng):
249
  "number": num,
250
  "count": count
251
  })
252
- print(f" βœ… {mask_number(num)} - {count} SMS")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
  return numbers_data
255
- except Exception as e:
256
- print(f" ❌ Error get_numbers: {e}")
257
  return []
258
 
259
  def get_sms_fast(account_id, rng, number):
@@ -264,14 +256,12 @@ def get_sms_fast(account_id, rng, number):
264
  try:
265
  date = get_search_date()
266
  cache_key = f"{rng}-{number}"
267
-
268
  if cache_key in account["sms_cache"]:
269
  timestamp, results = account["sms_cache"][cache_key]
270
  if time.time() - timestamp < 5:
271
- print(f" πŸ’Ύ Cache: {len(results)} SMS")
272
  return results
273
 
274
- print(f" πŸ“¨ AMBIL SMS: {mask_number(number)}")
275
  r = account["session"].post(GET_SMS_URL, data={
276
  "_token": account["csrf"],
277
  "start": date,
@@ -297,14 +287,12 @@ def get_sms_fast(account_id, rng, number):
297
  otp = extract_otp(sms)
298
  if otp:
299
  results.append((service, sms, otp))
300
- print(f" βœ… OTP: {otp} - {service}")
301
  except:
302
  continue
303
 
304
  account["sms_cache"][cache_key] = (time.time(), results)
305
  return results
306
- except Exception as e:
307
- print(f" ❌ Error get_sms: {e}")
308
  return []
309
 
310
  def add_otp_log(account_id, country, number, service, otp, sms):
@@ -333,7 +321,6 @@ def add_otp_log(account_id, country, number, service, otp, sms):
333
 
334
  broadcast_sse(log_entry)
335
  save_accounts(accounts)
336
- print(f" πŸ“Š OTP LOGGED: {otp}")
337
  return log_entry
338
 
339
  def broadcast_sse(data):
@@ -349,6 +336,7 @@ def broadcast_sse(data):
349
 
350
  @app.route('/')
351
  def home():
 
352
  all_logs = []
353
  for acc_id, acc in accounts.items():
354
  if acc.get("otp_logs"):
@@ -378,14 +366,17 @@ def home():
378
 
379
  all_services = list(set([log.get('service') for log in all_logs if log.get('service')]))
380
 
 
 
 
 
 
381
  accounts_list = []
382
  for acc_id, acc in accounts.items():
383
  accounts_list.append({
384
  "id": acc_id,
385
  "username": acc.get("username", "Unknown"),
386
- "status": acc.get("status", False),
387
- "bot": "βœ…" if acc.get("bot_token") else "❌",
388
- "chat": acc.get("chat_id", "-")[:5] + "..." if acc.get("chat_id") and len(acc.get("chat_id")) > 5 else acc.get("chat_id", "-")
389
  })
390
 
391
  html = f"""
@@ -404,6 +395,8 @@ def home():
404
  .header-top {{ display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 15px; }}
405
  .title h1 {{ font-size: 28px; font-weight: 700; background: linear-gradient(135deg, #00f2fe, #4facfe); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }}
406
  .title p {{ color: #8b949e; font-size: 14px; }}
 
 
407
  .stats-grid {{ display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-top: 20px; }}
408
  .stat-card {{ background: #1a1f2c; padding: 20px; border-radius: 20px; border: 1px solid #2d3540; }}
409
  .stat-label {{ color: #8b949e; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }}
@@ -432,9 +425,10 @@ def home():
432
  .search-input {{ width: 100%; padding: 14px 20px 14px 48px; background: #1a1f2c; border: 1px solid #2d3540; border-radius: 100px; color: #e4e6eb; font-size: 15px; }}
433
  .filter-box {{ flex: 1; min-width: 150px; }}
434
  .filter-select {{ width: 100%; padding: 14px 20px; background: #1a1f2c; border: 1px solid #2d3540; border-radius: 100px; color: #e4e6eb; font-size: 15px; cursor: pointer; }}
 
435
 
436
  .otp-section {{ background: #0f131c; border-radius: 24px; padding: 28px; border: 1px solid #2d3540; overflow-x: auto; }}
437
- table {{ width: 100%; border-collapse: collapse; min-width: 1000px; }}
438
  th {{ text-align: left; padding: 16px 12px; background: #1a1f2c; color: #00f2fe; font-weight: 600; font-size: 13px; text-transform: uppercase; border-bottom: 2px solid #2d3540; }}
439
  td {{ padding: 16px 12px; border-bottom: 1px solid #262c38; font-size: 14px; }}
440
  .otp-badge {{ background: #002b36; color: #00f2fe; font-family: monospace; font-size: 16px; font-weight: 700; padding: 6px 14px; border-radius: 100px; border: 1px solid #00f2fe40; cursor: pointer; user-select: all; }}
@@ -442,12 +436,12 @@ def home():
442
  .account-badge {{ background: #4a3a2d; padding: 4px 10px; border-radius: 20px; font-size: 11px; display: inline-block; }}
443
  .whatsapp {{ background: #25D36620; color: #25D366; border: 1px solid #25D36640; }}
444
  .telegram {{ background: #26A5E420; color: #26A5E4; border: 1px solid #26A5E440; }}
445
- .google {{ background: #EA433520; color: #EA4335; border: 1px solid #EA433540; }}
446
- .facebook {{ background: #4267B220; color: #4267B2; border: 1px solid #4267B240; }}
447
- .temu {{ background: #FF6B6B20; color: #FF6B6B; border: 1px solid #FF6B6B40; }}
448
  .new-row {{ animation: fadeIn 0.3s ease; background: linear-gradient(90deg, #00f2fe10, transparent); }}
449
- @keyframes fadeIn {{ from {{ opacity: 0; transform: translateY(-10px); }} to {{ opacity: 1; transform: translateY(0); }} }}
450
  .toast {{ position: fixed; bottom: 24px; right: 24px; background: #00f2fe; color: #000; padding: 14px 28px; border-radius: 100px; font-weight: 600; z-index: 9999; }}
 
451
  </style>
452
  </head>
453
  <body>
@@ -458,15 +452,17 @@ def home():
458
  <h1>OTP MULTI ACCOUNT Β· FOURSTORE</h1>
459
  <p>{CUSTOM_DOMAIN}</p>
460
  </div>
461
- <div class="stats-grid">
462
- <div class="stat-card"><div class="stat-label">Total Akun</div><div class="stat-value">{len(accounts)}</div></div>
463
- <div class="stat-card"><div class="stat-label">Akun Online</div><div class="stat-value">{sum(1 for a in accounts.values() if a.get('status'))}</div></div>
464
- <div class="stat-card"><div class="stat-label">Total OTP</div><div class="stat-value">{sum(len(a.get('sent_cache', [])) for a in accounts.values())}</div></div>
465
- <div class="stat-card"><div class="stat-label">WIB</div><div class="stat-value" id="wib-time">{get_wib_time().strftime('%H:%M:%S')}</div></div>
466
- </div>
 
467
  </div>
468
  </div>
469
 
 
470
  <div class="add-account-form">
471
  <h3 style="margin-bottom: 15px;">βž• Tambah Akun Baru</h3>
472
  <form action="/add_account" method="POST" class="form-grid">
@@ -478,10 +474,12 @@ def home():
478
  </form>
479
  </div>
480
 
 
481
  <div class="account-grid">
482
  {generate_account_cards(accounts_list)}
483
  </div>
484
 
 
485
  <div class="search-section">
486
  <div class="search-box">
487
  <span class="search-icon">πŸ”</span>
@@ -490,25 +488,36 @@ def home():
490
  </form>
491
  </div>
492
  <div class="filter-box">
493
- <select class="filter-select" name="service" onchange="window.location.href=updateQuery('service', this.value)">
494
  <option value="">πŸ“‹ Semua Service</option>
495
  {''.join([f'<option value="{s}" {"selected" if filter_service == s else ""}>πŸ“± {s}</option>' for s in sorted(all_services)])}
496
  </select>
497
  </div>
498
  <div class="filter-box">
499
- <select class="filter-select" name="account" onchange="window.location.href=updateQuery('account', this.value)">
500
  <option value="">πŸ‘€ Semua Akun</option>
501
  {''.join([f'<option value="{a["id"]}" {"selected" if filter_account == a["id"] else ""}>πŸ‘€ {a["username"][:15]}</option>' for a in accounts_list])}
502
  </select>
503
  </div>
504
- <a href="/" class="btn btn-small">Reset</a>
505
  <span class="result-count">πŸ“Š {len(all_logs)} hasil</span>
506
  </div>
507
 
 
508
  <div class="otp-section">
509
  <h3 style="margin-bottom: 20px;">πŸ“¨ OTP TERBARU <span style="background:#00f2fe20; padding:4px 12px; border-radius:100px; font-size:12px;">LIVE</span></h3>
510
  <table>
511
- <thead><tr><th>WIB</th><th>Akun</th><th>Country</th><th>Number</th><th>Service</th><th>OTP</th><th>Message</th></tr></thead>
 
 
 
 
 
 
 
 
 
 
512
  <tbody id="otp-table-body">
513
  {generate_otp_rows(all_logs, search_query)}
514
  </tbody>
@@ -518,22 +527,28 @@ def home():
518
 
519
  <script>
520
  let eventSource;
 
521
  function connectSSE() {{
522
  eventSource = new EventSource('/stream');
523
  eventSource.onmessage = function(e) {{
524
  try {{
525
  const data = JSON.parse(e.data);
526
- if (data.otp) location.reload();
 
 
527
  }} catch(err) {{}}
528
  }};
529
  eventSource.onerror = function() {{ setTimeout(connectSSE, 3000); }};
530
  }}
531
 
532
- function updateQuery(key, value) {{
533
  const url = new URL(window.location.href);
534
- if (value) url.searchParams.set(key, value);
535
- else url.searchParams.delete(key);
536
- return url.toString();
 
 
 
537
  }}
538
 
539
  function loginAccount(accountId) {{
@@ -572,6 +587,9 @@ def home():
572
  return html
573
 
574
  def generate_account_cards(accounts_list):
 
 
 
575
  html = ""
576
  for acc in accounts_list:
577
  status_class = "online" if acc["status"] else "offline"
@@ -579,21 +597,19 @@ def generate_account_cards(accounts_list):
579
  <div class="account-card {status_class}">
580
  <div class="account-status status-{status_class}"></div>
581
  <h4>{acc['username'][:20]}{'...' if len(acc['username']) > 20 else ''}</h4>
582
- <p style="margin-top:8px; color:#8b949e;">Bot: {acc['bot']} | Chat: {acc['chat']}</p>
583
  <div class="account-actions">
584
  <button onclick="loginAccount('{acc['id']}')" class="btn btn-small">Login</button>
585
  <button onclick="logoutAccount('{acc['id']}')" class="btn btn-small btn-danger">Logout</button>
586
- <a href="/delete_account/{acc['id']}" class="btn btn-small btn-danger" onclick="return confirm('Hapus akun?')">Hapus</a>
587
  </div>
588
  </div>
589
  """
590
- if not accounts_list:
591
- html = "<div style='grid-column:1/-1; text-align:center; padding:40px; color:#8b949e;'>Belum ada akun. Tambah akun di atas!</div>"
592
  return html
593
 
594
  def generate_otp_rows(logs, search_query):
595
  if not logs:
596
- return '<tr><td colspan="7" style="text-align:center; padding:60px; color:#8b949e;">πŸ” Belum ada OTP</td></tr>'
597
 
598
  rows = ""
599
  for log in logs[:100]:
@@ -605,7 +621,7 @@ def generate_otp_rows(logs, search_query):
605
  account = log.get('account_username', 'Unknown')[:15]
606
 
607
  if search_query:
608
- country = re.sub(f'({re.escape(search_query)})', r'<span class="highlight" style="background:#00f2fe30; border-radius:4px;">\1</span>', country, flags=re.I)
609
  number = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', number, flags=re.I)
610
  otp = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', otp, flags=re.I)
611
 
@@ -626,13 +642,17 @@ def generate_otp_rows(logs, search_query):
626
  @app.route('/add_account', methods=['POST'])
627
  def add_account_route():
628
  account_id = str(uuid.uuid4())[:8]
629
- print(f"\nβž• TAMBAH AKUN BARU: {request.form['username']}")
 
 
 
 
630
  accounts[account_id] = {
631
  "id": account_id,
632
- "username": request.form['username'],
633
- "password": request.form['password'],
634
- "bot_token": request.form.get('bot_token', ''),
635
- "chat_id": request.form.get('chat_id', ''),
636
  "session": None,
637
  "csrf": None,
638
  "status": False,
@@ -645,45 +665,39 @@ def add_account_route():
645
  "created_at": time.time()
646
  }
647
  save_accounts(accounts)
648
- print(f"βœ… Akun ditambahkan: {account_id}")
649
  return redirect('/')
650
 
651
  @app.route('/delete_account/<account_id>')
652
  def delete_account_route(account_id):
653
  if account_id in accounts:
654
- print(f"\nπŸ—‘οΈ HAPUS AKUN: {accounts[account_id]['username']}")
655
  del accounts[account_id]
656
  save_accounts(accounts)
657
- print(f"βœ… Akun dihapus")
658
  return redirect('/')
659
 
660
  @app.route('/login_account/<account_id>', methods=['POST'])
661
  def login_account_route(account_id):
662
  if account_id in accounts:
663
  acc = accounts[account_id]
664
- print(f"\nπŸ” Mencoba login untuk: {acc['username']}")
665
- success, msg = login_account(account_id, acc['username'], acc['password'])
666
- print(f"πŸ“ Result: {success} - {msg}")
667
-
 
 
 
668
  if success:
669
- print(f"βœ… Login BERHASIL! Memulai thread scraper...")
670
  thread = Thread(target=run_account_scraper, args=(account_id,), daemon=True)
671
  thread.start()
672
- print(f"βœ… Thread scraper dimulai untuk {acc['username']}")
673
- else:
674
- print(f"❌ Login GAGAL: {msg}")
675
-
676
  return redirect('/')
677
 
678
  @app.route('/logout_account/<account_id>', methods=['POST'])
679
  def logout_account_route(account_id):
680
  if account_id in accounts:
681
- print(f"\nπŸ”Œ LOGOUT: {accounts[account_id]['username']}")
682
  accounts[account_id]["status"] = False
683
  accounts[account_id]["session"] = None
684
  accounts[account_id]["csrf"] = None
685
  save_accounts(accounts)
686
- print(f"βœ… Logout berhasil")
687
  return redirect('/')
688
 
689
  @app.route('/stream')
@@ -705,25 +719,18 @@ def run_account_scraper(account_id):
705
  return
706
 
707
  username = account['username']
708
- print(f"\nπŸš€ STARTING SCRAPER FOR: {username}")
709
- loop_count = 0
710
 
711
  while account.get("status"):
712
- loop_count += 1
713
  try:
714
- print(f"\n{'='*60}")
715
- print(f"πŸ”„ [{username}] LOOP #{loop_count}")
716
- print(f"{'='*60}")
717
-
718
  if time.time() - account.get("last_cleanup", 0) > 300:
719
  account["sms_cache"] = {}
720
  account["sms_counter"] = {}
721
  account["range_counter"] = {}
722
  account["last_cleanup"] = time.time()
723
- print(f"🧹 [{username}] Cache cleared")
724
 
725
  ranges_data = get_ranges_with_count(account_id)
726
- print(f"πŸ“Š [{username}] Total ranges: {len(ranges_data)}")
727
 
728
  for range_item in ranges_data:
729
  if not account.get("status"):
@@ -733,15 +740,12 @@ def run_account_scraper(account_id):
733
  current_count = range_item["count"]
734
  prev_count = account["range_counter"].get(rng, 0)
735
 
736
- print(f"\n πŸ“Œ RANGE: {rng} | current: {current_count} | prev: {prev_count}")
737
-
738
  if current_count > prev_count:
739
  country = clean_country(rng)
740
- print(f" πŸ”₯ RANGE BERUBAH: {country} | {prev_count} β†’ {current_count}")
741
  account["range_counter"][rng] = current_count
742
 
743
  numbers_data = get_numbers_with_count(account_id, rng)
744
- print(f" πŸ“ž Total nomor: {len(numbers_data)}")
745
 
746
  for number_item in numbers_data:
747
  if not account.get("status"):
@@ -752,15 +756,11 @@ def run_account_scraper(account_id):
752
  key = f"{rng}-{num}"
753
  prev_num_count = account["sms_counter"].get(key, 0)
754
 
755
- print(f"\n πŸ“± Nomor: {mask_number(num)} | count: {num_count} | prev: {prev_num_count}")
756
-
757
  if num_count > prev_num_count:
758
- print(f" πŸ“¨ SMS baru: {prev_num_count} β†’ {num_count}")
759
  account["sms_counter"][key] = num_count
760
 
761
  all_sms = get_sms_fast(account_id, rng, num)
762
  new_sms = all_sms[prev_num_count:]
763
- print(f" πŸ“¦ Total SMS baru: {len(new_sms)}")
764
 
765
  for service, sms, otp in new_sms:
766
  if otp:
@@ -768,33 +768,27 @@ def run_account_scraper(account_id):
768
  if sms_id not in account["sent_cache"]:
769
  masked = mask_number(num)
770
  msg = f"πŸ”” *NEW OTP*\n🌍 {country}\nπŸ“ž `{masked}`\nπŸ’¬ {service}\nπŸ” `{otp}`\n\n{sms[:300]}"
771
- print(f" πŸ“€ Mengirim OTP {otp} ke Telegram...")
772
-
773
  if tg_send(account_id, msg):
774
- if "sent_cache" not in account:
775
- account["sent_cache"] = []
776
  account["sent_cache"].append(sms_id)
777
  if len(account["sent_cache"]) > 1000:
778
  account["sent_cache"] = account["sent_cache"][-1000:]
779
  add_otp_log(account_id, country, masked, service, otp, sms)
780
- print(f" βœ… OTP {otp} TERKIRIM!")
781
 
782
  time.sleep(0.5)
783
- else:
784
- print(f" ⏭️ Range tidak berubah")
785
 
786
- print(f"\n⏳ [{username}] Tidur 2 detik...")
787
  time.sleep(2)
788
 
789
  except Exception as e:
790
- print(f"❌ ERROR in scraper for {username}: {str(e)}")
791
  time.sleep(5)
792
 
793
- print(f"\nπŸ›‘ SCRAPER STOPPED FOR: {username}")
794
 
795
  def run_server():
796
  app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)
797
 
 
798
  Thread(target=run_server, daemon=True).start()
799
 
800
  def main():
@@ -803,29 +797,31 @@ def main():
803
  print(" ⚑ PORT: 7860")
804
  print(f" 🌐 DOMAIN: {CUSTOM_DOMAIN}")
805
  print(" πŸ“ Data tersimpan di accounts.json")
806
- print(" πŸ“‹ LOGGING: FULL DETAIL")
807
  print("="*60 + "\n")
808
 
809
- print(f"πŸ“Š Total akun terload: {len(accounts)}")
810
-
811
  for acc_id, acc in accounts.items():
812
- print(f"\nπŸ“ Account: {acc['username']}")
813
- print(f" Status: {'ONLINE' if acc.get('status') else 'OFFLINE'}")
814
-
815
  if acc.get("status"):
816
- print(f" πŸ”„ Mencoba login ulang...")
817
- success, msg = login_account(acc_id, acc['username'], acc['password'])
 
 
 
 
 
 
818
  if success:
819
  thread = Thread(target=run_account_scraper, args=(acc_id,), daemon=True)
820
  thread.start()
821
- print(f" βœ… {acc['username']} online")
822
  else:
823
- print(f" ❌ {acc['username']} offline: {msg}")
824
  acc["status"] = False
825
  save_accounts(accounts)
826
 
827
  print("\nβœ… BOT SIAP! Dashboard: http://localhost:7860")
828
 
 
829
  while True:
830
  time.sleep(60)
831
 
 
18
  GET_SMS_URL = f"{BASE}/portal/sms/received/getsms/number/sms"
19
 
20
  TELEGRAM_PROXY_URL = "https://danihitambangetjir.termai.cc/api/proxy"
21
+ CUSTOM_DOMAIN = "https://fourstore-otp.hf.space"
22
  ACCOUNTS_FILE = "accounts.json"
23
 
24
+ # Load accounts dari file
25
  def load_accounts():
26
  if os.path.exists(ACCOUNTS_FILE):
27
  try:
 
35
  with open(ACCOUNTS_FILE, 'w') as f:
36
  json.dump(accounts, f, indent=2)
37
 
38
+ # Inisialisasi accounts
39
  accounts = load_accounts()
40
+
41
+ # Tambahkan runtime data ke setiap account
42
  for acc_id in accounts:
43
  accounts[acc_id]["session"] = None
44
  accounts[acc_id]["csrf"] = None
 
51
  accounts[acc_id]["last_cleanup"] = time.time()
52
 
53
  app = Flask('')
54
+ app.secret_key = "fourstore-multi-account-secret"
55
  sse_clients = []
56
 
57
  def get_wib_time():
 
61
  wib = get_wib_time()
62
  return (wib - timedelta(days=1)).strftime("%Y-%m-%d") if wib.hour < 7 else wib.strftime("%Y-%m-%d")
63
 
64
+ def login_account(account_id, username, password, bot_token, chat_id):
65
  try:
66
+ print(f"\nπŸ” Login untuk: {username}")
 
67
  session = httpx.Client(follow_redirects=True, timeout=30.0)
68
 
 
69
  r = session.get(LOGIN_URL, timeout=30)
 
 
 
 
 
 
70
  soup = BeautifulSoup(r.text, "html.parser")
71
  token = soup.find("input", {"name": "_token"})
72
  if not token:
 
 
73
  return False, "Token tidak ditemukan"
74
 
75
  csrf_token = token.get("value")
 
 
 
76
  r = session.post(LOGIN_URL, data={
77
  "_token": csrf_token,
78
  "email": username,
79
  "password": password
80
  }, timeout=30)
81
+
 
82
  if "dashboard" in r.text.lower() or "logout" in r.text.lower():
 
 
 
83
  accounts[account_id]["session"] = session
84
  accounts[account_id]["csrf"] = csrf_token
85
  accounts[account_id]["status"] = True
86
+ accounts[account_id]["username"] = username
87
+ accounts[account_id]["password"] = password
88
+ accounts[account_id]["bot_token"] = bot_token
89
+ accounts[account_id]["chat_id"] = chat_id
90
  accounts[account_id]["last_login"] = time.time()
91
  save_accounts(accounts)
 
92
  return True, "Login berhasil"
93
  else:
94
+ return False, "Login gagal"
 
 
 
95
  except Exception as e:
 
96
  return False, str(e)
97
 
98
  def tg_send(account_id, msg):
99
  try:
100
  account = accounts.get(account_id)
101
  if not account or not account.get("bot_token") or not account.get("chat_id"):
 
102
  return False
103
 
104
  url = f"{TELEGRAM_PROXY_URL}?url=https://api.telegram.org/bot{account['bot_token']}/sendMessage"
 
107
  "text": msg,
108
  "parse_mode": "Markdown"
109
  }
110
+ httpx.post(url, json=payload, timeout=30)
 
 
111
  return True
112
+ except:
 
113
  return False
114
 
115
  def clean_country(rng):
 
150
  def get_ranges_with_count(account_id):
151
  account = accounts.get(account_id)
152
  if not account or not account.get("session") or not account.get("csrf"):
 
153
  return []
154
 
155
  try:
156
  date = get_search_date()
 
 
157
  r = account["session"].post(GET_RANGE_URL, data={
158
  "_token": account["csrf"],
159
  "from": date,
160
  "to": date
161
  }, timeout=15)
162
+
 
 
163
  soup = BeautifulSoup(r.text, "html.parser")
164
  ranges_data = []
165
 
 
175
  "name": rng,
176
  "count": count
177
  })
 
178
 
179
  return ranges_data
180
+ except:
 
181
  return []
182
 
183
  def get_numbers_with_count(account_id, rng):
 
187
 
188
  try:
189
  date = get_search_date()
 
 
190
  r = account["session"].post(GET_NUMBER_URL, data={
191
  "_token": account["csrf"],
192
  "start": date,
 
222
  "number": num,
223
  "count": count
224
  })
225
+
226
+ if len(numbers_data) == 0:
227
+ for div in soup.find_all("div", class_="col-sm-4"):
228
+ text = div.get_text(strip=True)
229
+ match = re.search(r'\b(\d{10,15})\b', text)
230
+ if match:
231
+ num = match.group(1)
232
+ parent = div.find_parent("div", class_="card")
233
+ count = 0
234
+ if parent:
235
+ p_tag = parent.find("p", class_="mb-0 pb-0")
236
+ if p_tag:
237
+ try:
238
+ count = int(p_tag.get_text(strip=True))
239
+ except:
240
+ count = 0
241
+
242
+ numbers_data.append({
243
+ "number": num,
244
+ "count": count
245
+ })
246
 
247
  return numbers_data
248
+ except:
 
249
  return []
250
 
251
  def get_sms_fast(account_id, rng, number):
 
256
  try:
257
  date = get_search_date()
258
  cache_key = f"{rng}-{number}"
259
+
260
  if cache_key in account["sms_cache"]:
261
  timestamp, results = account["sms_cache"][cache_key]
262
  if time.time() - timestamp < 5:
 
263
  return results
264
 
 
265
  r = account["session"].post(GET_SMS_URL, data={
266
  "_token": account["csrf"],
267
  "start": date,
 
287
  otp = extract_otp(sms)
288
  if otp:
289
  results.append((service, sms, otp))
 
290
  except:
291
  continue
292
 
293
  account["sms_cache"][cache_key] = (time.time(), results)
294
  return results
295
+ except:
 
296
  return []
297
 
298
  def add_otp_log(account_id, country, number, service, otp, sms):
 
321
 
322
  broadcast_sse(log_entry)
323
  save_accounts(accounts)
 
324
  return log_entry
325
 
326
  def broadcast_sse(data):
 
336
 
337
  @app.route('/')
338
  def home():
339
+ # Gabungkan semua log dari semua akun
340
  all_logs = []
341
  for acc_id, acc in accounts.items():
342
  if acc.get("otp_logs"):
 
366
 
367
  all_services = list(set([log.get('service') for log in all_logs if log.get('service')]))
368
 
369
+ # Hitung total OTP dari semua akun
370
+ total_otp = sum(len(acc.get('sent_cache', [])) for acc in accounts.values())
371
+ today_otp = len(all_logs)
372
+
373
+ # Daftar akun untuk filter
374
  accounts_list = []
375
  for acc_id, acc in accounts.items():
376
  accounts_list.append({
377
  "id": acc_id,
378
  "username": acc.get("username", "Unknown"),
379
+ "status": acc.get("status", False)
 
 
380
  })
381
 
382
  html = f"""
 
395
  .header-top {{ display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 15px; }}
396
  .title h1 {{ font-size: 28px; font-weight: 700; background: linear-gradient(135deg, #00f2fe, #4facfe); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }}
397
  .title p {{ color: #8b949e; font-size: 14px; }}
398
+ .status-badge {{ padding: 10px 24px; border-radius: 100px; font-weight: 600; font-size: 14px;
399
+ background: #0a4d3c; color: #a0f0d0; border: 1px solid #1a6e5a; }}
400
  .stats-grid {{ display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-top: 20px; }}
401
  .stat-card {{ background: #1a1f2c; padding: 20px; border-radius: 20px; border: 1px solid #2d3540; }}
402
  .stat-label {{ color: #8b949e; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; }}
 
425
  .search-input {{ width: 100%; padding: 14px 20px 14px 48px; background: #1a1f2c; border: 1px solid #2d3540; border-radius: 100px; color: #e4e6eb; font-size: 15px; }}
426
  .filter-box {{ flex: 1; min-width: 150px; }}
427
  .filter-select {{ width: 100%; padding: 14px 20px; background: #1a1f2c; border: 1px solid #2d3540; border-radius: 100px; color: #e4e6eb; font-size: 15px; cursor: pointer; }}
428
+ .clear-btn {{ padding: 8px 16px; background: #2d3a4a; border: none; border-radius: 100px; color: white; font-size: 13px; cursor: pointer; text-decoration: none; }}
429
 
430
  .otp-section {{ background: #0f131c; border-radius: 24px; padding: 28px; border: 1px solid #2d3540; overflow-x: auto; }}
431
+ table {{ width: 100%; border-collapse: collapse; min-width: 1100px; }}
432
  th {{ text-align: left; padding: 16px 12px; background: #1a1f2c; color: #00f2fe; font-weight: 600; font-size: 13px; text-transform: uppercase; border-bottom: 2px solid #2d3540; }}
433
  td {{ padding: 16px 12px; border-bottom: 1px solid #262c38; font-size: 14px; }}
434
  .otp-badge {{ background: #002b36; color: #00f2fe; font-family: monospace; font-size: 16px; font-weight: 700; padding: 6px 14px; border-radius: 100px; border: 1px solid #00f2fe40; cursor: pointer; user-select: all; }}
 
436
  .account-badge {{ background: #4a3a2d; padding: 4px 10px; border-radius: 20px; font-size: 11px; display: inline-block; }}
437
  .whatsapp {{ background: #25D36620; color: #25D366; border: 1px solid #25D36640; }}
438
  .telegram {{ background: #26A5E420; color: #26A5E4; border: 1px solid #26A5E440; }}
439
+ .number {{ font-family: monospace; }}
440
+ .empty-state {{ text-align: center; padding: 60px; color: #8b949e; }}
441
+ .highlight {{ background: #00f2fe30; border-radius: 4px; padding: 0 2px; }}
442
  .new-row {{ animation: fadeIn 0.3s ease; background: linear-gradient(90deg, #00f2fe10, transparent); }}
 
443
  .toast {{ position: fixed; bottom: 24px; right: 24px; background: #00f2fe; color: #000; padding: 14px 28px; border-radius: 100px; font-weight: 600; z-index: 9999; }}
444
+ @keyframes fadeIn {{ from {{ opacity: 0; transform: translateY(-10px); }} to {{ opacity: 1; transform: translateY(0); }} }}
445
  </style>
446
  </head>
447
  <body>
 
452
  <h1>OTP MULTI ACCOUNT Β· FOURSTORE</h1>
453
  <p>{CUSTOM_DOMAIN}</p>
454
  </div>
455
+ <div class="status-badge">● ONLINE</div>
456
+ </div>
457
+ <div class="stats-grid">
458
+ <div class="stat-card"><div class="stat-label">Total Akun</div><div class="stat-value">{len(accounts)}</div></div>
459
+ <div class="stat-card"><div class="stat-label">Akun Online</div><div class="stat-value">{sum(1 for a in accounts.values() if a.get('status'))}</div></div>
460
+ <div class="stat-card"><div class="stat-label">Total OTP</div><div class="stat-value">{total_otp}</div></div>
461
+ <div class="stat-card"><div class="stat-label">WIB</div><div class="stat-value" id="wib-time">{get_wib_time().strftime('%H:%M:%S')}</div></div>
462
  </div>
463
  </div>
464
 
465
+ <!-- Form Tambah Akun -->
466
  <div class="add-account-form">
467
  <h3 style="margin-bottom: 15px;">βž• Tambah Akun Baru</h3>
468
  <form action="/add_account" method="POST" class="form-grid">
 
474
  </form>
475
  </div>
476
 
477
+ <!-- Daftar Akun -->
478
  <div class="account-grid">
479
  {generate_account_cards(accounts_list)}
480
  </div>
481
 
482
+ <!-- Search & Filter -->
483
  <div class="search-section">
484
  <div class="search-box">
485
  <span class="search-icon">πŸ”</span>
 
488
  </form>
489
  </div>
490
  <div class="filter-box">
491
+ <select class="filter-select" name="service" onchange="updateFilter('service', this.value)">
492
  <option value="">πŸ“‹ Semua Service</option>
493
  {''.join([f'<option value="{s}" {"selected" if filter_service == s else ""}>πŸ“± {s}</option>' for s in sorted(all_services)])}
494
  </select>
495
  </div>
496
  <div class="filter-box">
497
+ <select class="filter-select" name="account" onchange="updateFilter('account', this.value)">
498
  <option value="">πŸ‘€ Semua Akun</option>
499
  {''.join([f'<option value="{a["id"]}" {"selected" if filter_account == a["id"] else ""}>πŸ‘€ {a["username"][:15]}</option>' for a in accounts_list])}
500
  </select>
501
  </div>
502
+ <a href="/" class="clear-btn">βœ• Reset</a>
503
  <span class="result-count">πŸ“Š {len(all_logs)} hasil</span>
504
  </div>
505
 
506
+ <!-- Tabel OTP -->
507
  <div class="otp-section">
508
  <h3 style="margin-bottom: 20px;">πŸ“¨ OTP TERBARU <span style="background:#00f2fe20; padding:4px 12px; border-radius:100px; font-size:12px;">LIVE</span></h3>
509
  <table>
510
+ <thead>
511
+ <tr>
512
+ <th>WIB</th>
513
+ <th>Akun</th>
514
+ <th>Country</th>
515
+ <th>Number</th>
516
+ <th>Service</th>
517
+ <th>OTP</th>
518
+ <th>Message</th>
519
+ </tr>
520
+ </thead>
521
  <tbody id="otp-table-body">
522
  {generate_otp_rows(all_logs, search_query)}
523
  </tbody>
 
527
 
528
  <script>
529
  let eventSource;
530
+
531
  function connectSSE() {{
532
  eventSource = new EventSource('/stream');
533
  eventSource.onmessage = function(e) {{
534
  try {{
535
  const data = JSON.parse(e.data);
536
+ if (data.otp) {{
537
+ location.reload();
538
+ }}
539
  }} catch(err) {{}}
540
  }};
541
  eventSource.onerror = function() {{ setTimeout(connectSSE, 3000); }};
542
  }}
543
 
544
+ function updateFilter(key, value) {{
545
  const url = new URL(window.location.href);
546
+ if (value) {{
547
+ url.searchParams.set(key, value);
548
+ }} else {{
549
+ url.searchParams.delete(key);
550
+ }}
551
+ window.location.href = url.toString();
552
  }}
553
 
554
  function loginAccount(accountId) {{
 
587
  return html
588
 
589
  def generate_account_cards(accounts_list):
590
+ if not accounts_list:
591
+ return '<div style="grid-column:1/-1; text-align:center; padding:40px; color:#8b949e;">Belum ada akun. Tambah akun di atas!</div>'
592
+
593
  html = ""
594
  for acc in accounts_list:
595
  status_class = "online" if acc["status"] else "offline"
 
597
  <div class="account-card {status_class}">
598
  <div class="account-status status-{status_class}"></div>
599
  <h4>{acc['username'][:20]}{'...' if len(acc['username']) > 20 else ''}</h4>
600
+ <p style="margin-top:8px; color:#8b949e;">Status: {'🟒 Online' if acc['status'] else 'πŸ”΄ Offline'}</p>
601
  <div class="account-actions">
602
  <button onclick="loginAccount('{acc['id']}')" class="btn btn-small">Login</button>
603
  <button onclick="logoutAccount('{acc['id']}')" class="btn btn-small btn-danger">Logout</button>
604
+ <a href="/delete_account/{acc['id']}" class="btn btn-small btn-danger" onclick="return confirm('Hapus akun ini?')">Hapus</a>
605
  </div>
606
  </div>
607
  """
 
 
608
  return html
609
 
610
  def generate_otp_rows(logs, search_query):
611
  if not logs:
612
+ return '<tr><td colspan="7" class="empty-state">πŸ” Belum ada OTP</td></tr>'
613
 
614
  rows = ""
615
  for log in logs[:100]:
 
621
  account = log.get('account_username', 'Unknown')[:15]
622
 
623
  if search_query:
624
+ country = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', country, flags=re.I)
625
  number = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', number, flags=re.I)
626
  otp = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', otp, flags=re.I)
627
 
 
642
  @app.route('/add_account', methods=['POST'])
643
  def add_account_route():
644
  account_id = str(uuid.uuid4())[:8]
645
+ username = request.form['username']
646
+ password = request.form['password']
647
+ bot_token = request.form.get('bot_token', '')
648
+ chat_id = request.form.get('chat_id', '')
649
+
650
  accounts[account_id] = {
651
  "id": account_id,
652
+ "username": username,
653
+ "password": password,
654
+ "bot_token": bot_token,
655
+ "chat_id": chat_id,
656
  "session": None,
657
  "csrf": None,
658
  "status": False,
 
665
  "created_at": time.time()
666
  }
667
  save_accounts(accounts)
 
668
  return redirect('/')
669
 
670
  @app.route('/delete_account/<account_id>')
671
  def delete_account_route(account_id):
672
  if account_id in accounts:
 
673
  del accounts[account_id]
674
  save_accounts(accounts)
 
675
  return redirect('/')
676
 
677
  @app.route('/login_account/<account_id>', methods=['POST'])
678
  def login_account_route(account_id):
679
  if account_id in accounts:
680
  acc = accounts[account_id]
681
+ success, msg = login_account(
682
+ account_id,
683
+ acc['username'],
684
+ acc['password'],
685
+ acc.get('bot_token', ''),
686
+ acc.get('chat_id', '')
687
+ )
688
  if success:
689
+ # Start thread scraper untuk akun ini
690
  thread = Thread(target=run_account_scraper, args=(account_id,), daemon=True)
691
  thread.start()
 
 
 
 
692
  return redirect('/')
693
 
694
  @app.route('/logout_account/<account_id>', methods=['POST'])
695
  def logout_account_route(account_id):
696
  if account_id in accounts:
 
697
  accounts[account_id]["status"] = False
698
  accounts[account_id]["session"] = None
699
  accounts[account_id]["csrf"] = None
700
  save_accounts(accounts)
 
701
  return redirect('/')
702
 
703
  @app.route('/stream')
 
719
  return
720
 
721
  username = account['username']
722
+ print(f"πŸš€ Starting scraper for {username}")
 
723
 
724
  while account.get("status"):
 
725
  try:
 
 
 
 
726
  if time.time() - account.get("last_cleanup", 0) > 300:
727
  account["sms_cache"] = {}
728
  account["sms_counter"] = {}
729
  account["range_counter"] = {}
730
  account["last_cleanup"] = time.time()
731
+ print(f"🧹 Cache cleared for {username}")
732
 
733
  ranges_data = get_ranges_with_count(account_id)
 
734
 
735
  for range_item in ranges_data:
736
  if not account.get("status"):
 
740
  current_count = range_item["count"]
741
  prev_count = account["range_counter"].get(rng, 0)
742
 
 
 
743
  if current_count > prev_count:
744
  country = clean_country(rng)
745
+ print(f"\nπŸ”₯ RANGE BERUBAH: {country} ({username})")
746
  account["range_counter"][rng] = current_count
747
 
748
  numbers_data = get_numbers_with_count(account_id, rng)
 
749
 
750
  for number_item in numbers_data:
751
  if not account.get("status"):
 
756
  key = f"{rng}-{num}"
757
  prev_num_count = account["sms_counter"].get(key, 0)
758
 
 
 
759
  if num_count > prev_num_count:
 
760
  account["sms_counter"][key] = num_count
761
 
762
  all_sms = get_sms_fast(account_id, rng, num)
763
  new_sms = all_sms[prev_num_count:]
 
764
 
765
  for service, sms, otp in new_sms:
766
  if otp:
 
768
  if sms_id not in account["sent_cache"]:
769
  masked = mask_number(num)
770
  msg = f"πŸ”” *NEW OTP*\n🌍 {country}\nπŸ“ž `{masked}`\nπŸ’¬ {service}\nπŸ” `{otp}`\n\n{sms[:300]}"
 
 
771
  if tg_send(account_id, msg):
 
 
772
  account["sent_cache"].append(sms_id)
773
  if len(account["sent_cache"]) > 1000:
774
  account["sent_cache"] = account["sent_cache"][-1000:]
775
  add_otp_log(account_id, country, masked, service, otp, sms)
776
+ print(f" βœ… OTP: {otp} - {service}")
777
 
778
  time.sleep(0.5)
 
 
779
 
 
780
  time.sleep(2)
781
 
782
  except Exception as e:
783
+ print(f"❌ Error in scraper for {username}: {e}")
784
  time.sleep(5)
785
 
786
+ print(f"πŸ›‘ Scraper stopped for {username}")
787
 
788
  def run_server():
789
  app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)
790
 
791
+ # Start server thread
792
  Thread(target=run_server, daemon=True).start()
793
 
794
  def main():
 
797
  print(" ⚑ PORT: 7860")
798
  print(f" 🌐 DOMAIN: {CUSTOM_DOMAIN}")
799
  print(" πŸ“ Data tersimpan di accounts.json")
 
800
  print("="*60 + "\n")
801
 
802
+ # Login otomatis untuk akun yang sudah login sebelumnya
 
803
  for acc_id, acc in accounts.items():
 
 
 
804
  if acc.get("status"):
805
+ print(f"πŸ”„ Auto-login untuk {acc['username']}...")
806
+ success, msg = login_account(
807
+ acc_id,
808
+ acc['username'],
809
+ acc['password'],
810
+ acc.get('bot_token', ''),
811
+ acc.get('chat_id', '')
812
+ )
813
  if success:
814
  thread = Thread(target=run_account_scraper, args=(acc_id,), daemon=True)
815
  thread.start()
816
+ print(f"βœ… {acc['username']} online")
817
  else:
818
+ print(f"❌ {acc['username']} offline: {msg}")
819
  acc["status"] = False
820
  save_accounts(accounts)
821
 
822
  print("\nβœ… BOT SIAP! Dashboard: http://localhost:7860")
823
 
824
+ # Keep main thread alive
825
  while True:
826
  time.sleep(60)
827