Fourstore commited on
Commit
3978bb5
·
verified ·
1 Parent(s): ad878b2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +172 -62
app.py CHANGED
@@ -85,8 +85,8 @@ def save_accounts_to_mongodb(accounts_dict):
85
  acc_copy = acc.copy()
86
  acc_copy.pop("session", None)
87
  acc_copy.pop("csrf", None)
88
- acc_copy.pop("otp_logs", None)
89
- acc_copy.pop("sent_cache", None)
90
  acc_copy.pop("sms_cache", None)
91
  acc_copy.pop("sms_counter", None)
92
  acc_copy.pop("range_counter", None)
@@ -109,7 +109,8 @@ def load_accounts_from_file():
109
  data[acc_id]["session"] = None
110
  data[acc_id]["csrf"] = None
111
  data[acc_id]["status"] = False
112
- data[acc_id]["otp_logs"] = []
 
113
  data[acc_id]["sent_cache"] = []
114
  data[acc_id]["sms_cache"] = {}
115
  data[acc_id]["sms_counter"] = {}
@@ -128,8 +129,8 @@ def save_accounts_to_file(accounts_dict):
128
  acc_copy = acc.copy()
129
  acc_copy.pop("session", None)
130
  acc_copy.pop("csrf", None)
131
- acc_copy.pop("otp_logs", None)
132
- acc_copy.pop("sent_cache", None)
133
  acc_copy.pop("sms_cache", None)
134
  acc_copy.pop("sms_counter", None)
135
  acc_copy.pop("range_counter", None)
@@ -151,12 +152,20 @@ app = Flask('')
151
  app.secret_key = "fourstore-multi-account-secret"
152
  sse_clients = []
153
 
 
 
 
 
 
154
  def get_wib_time():
155
  return datetime.now(timezone.utc) + timedelta(hours=7)
156
 
 
 
 
157
  def get_search_date():
158
- wib = get_wib_time()
159
- return (wib - timedelta(days=1)).strftime("%Y-%m-%d") if wib.hour < 7 else wib.strftime("%Y-%m-%d")
160
 
161
  def login_account(account_id, username, password, bot_token, chat_id):
162
  try:
@@ -242,22 +251,32 @@ def map_service(raw):
242
  if 'instagram' in s or 'ig' in s: return "INSTAGRAM"
243
  if 'tiktok' in s: return "TIKTOK"
244
  if 'temu' in s: return "TEMU"
 
 
 
 
245
  return raw.upper()
246
 
247
  def extract_otp(text):
248
  if not text: return None
249
- m = re.search(r"\b(\d{3}[- ]?\d{3})\b", text)
250
  if m:
251
- return m.group(0).replace("-", "").replace(" ", "")
252
- m = re.search(r"\b(\d{4,6})\b", text)
253
  if m:
254
  return m.group(0)
 
 
 
255
  digits = re.findall(r'\d+', text)
256
  for d in digits:
257
  if 4 <= len(d) <= 6:
258
  return d
259
  return None
260
 
 
 
 
261
  def get_ranges_with_count(account_id):
262
  account = accounts.get(account_id)
263
  if not account or not account.get("session") or not account.get("csrf"):
@@ -288,7 +307,8 @@ def get_ranges_with_count(account_id):
288
  })
289
 
290
  return ranges_data
291
- except:
 
292
  return []
293
 
294
  def get_numbers_with_count(account_id, rng):
@@ -356,7 +376,8 @@ def get_numbers_with_count(account_id, rng):
356
  })
357
 
358
  return numbers_data
359
- except:
 
360
  return []
361
 
362
  def get_sms_fast(account_id, rng, number):
@@ -403,24 +424,49 @@ def get_sms_fast(account_id, rng, number):
403
 
404
  account["sms_cache"][cache_key] = (time.time(), results)
405
  return results
406
- except:
 
407
  return []
408
 
409
- def add_otp_log(account_id, country, number, service, otp, sms):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  account = accounts.get(account_id)
411
  if not account:
412
  return
413
 
414
  wib = get_wib_time()
 
 
 
415
  log_entry = {
416
- "time": wib.strftime("%H:%M:%S"),
 
 
417
  "country": country,
418
  "number": number,
419
  "service": service,
420
  "otp": otp,
421
- "sms": sms[:80] + "..." if len(sms) > 80 else sms,
422
  "account_id": account_id,
423
- "account_username": mask_email(account.get("username", "Unknown"))
 
424
  }
425
 
426
  if "otp_logs" not in account:
@@ -430,7 +476,18 @@ def add_otp_log(account_id, country, number, service, otp, sms):
430
  if len(account["otp_logs"]) > 100:
431
  account["otp_logs"] = account["otp_logs"][:100]
432
 
 
 
 
 
433
  broadcast_sse(log_entry)
 
 
 
 
 
 
 
434
  return log_entry
435
 
436
  def broadcast_sse(data):
@@ -446,15 +503,7 @@ def broadcast_sse(data):
446
 
447
  @app.route('/')
448
  def home():
449
- all_logs = []
450
- for acc_id, acc in accounts.items():
451
- if acc.get("otp_logs"):
452
- for log in acc["otp_logs"]:
453
- log_copy = log.copy()
454
- log_copy["account_username"] = mask_email(acc.get("username", "Unknown"))
455
- all_logs.append(log_copy)
456
-
457
- all_logs = sorted(all_logs, key=lambda x: x.get("time", ""), reverse=True)
458
 
459
  search_query = request.args.get('q', '').lower()
460
  filter_service = request.args.get('service', '')
@@ -464,15 +513,21 @@ def home():
464
  search_query in log.get('country', '').lower() or
465
  search_query in log.get('number', '').lower() or
466
  search_query in log.get('otp', '').lower() or
467
- search_query in log.get('sms', '').lower()]
 
468
 
469
  if filter_service:
470
  all_logs = [log for log in all_logs if log.get('service') == filter_service]
471
 
472
  all_services = list(set([log.get('service') for log in all_logs if log.get('service')]))
473
 
474
- total_otp = sum(len(acc.get('sent_cache', [])) for acc in accounts.values())
475
- today_otp = len(all_logs)
 
 
 
 
 
476
 
477
  html = f"""
478
  <!DOCTYPE html>
@@ -513,19 +568,31 @@ def home():
513
  .filter-select {{ width: 100%; padding: 14px 20px; background: #1a1f2c; border: 1px solid #2d3540; border-radius: 100px; color: #e4e6eb; font-size: 15px; cursor: pointer; }}
514
  .clear-btn {{ padding: 8px 16px; background: #2d3a4a; border: none; border-radius: 100px; color: white; font-size: 13px; cursor: pointer; text-decoration: none; }}
515
 
 
 
 
516
  .otp-section {{ background: #0f131c; border-radius: 24px; padding: 28px; border: 1px solid #2d3540; overflow-x: auto; }}
517
- table {{ width: 100%; border-collapse: collapse; min-width: 1000px; }}
518
  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; }}
519
  td {{ padding: 16px 12px; border-bottom: 1px solid #262c38; font-size: 14px; }}
520
  .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; }}
521
- .service-badge {{ background: #2d3a4a; padding: 6px 14px; border-radius: 100px; font-size: 12px; font-weight: 600; display: inline-block; }}
522
  .whatsapp {{ background: #25D36620; color: #25D366; border: 1px solid #25D36640; }}
523
  .telegram {{ background: #26A5E420; color: #26A5E4; border: 1px solid #26A5E440; }}
 
 
 
 
 
 
 
 
524
  .number {{ font-family: monospace; }}
525
  .empty-state {{ text-align: center; padding: 60px; color: #8b949e; }}
526
  .highlight {{ background: #00f2fe30; border-radius: 4px; padding: 0 2px; }}
527
  .new-row {{ animation: fadeIn 0.3s ease; background: linear-gradient(90deg, #00f2fe10, transparent); }}
528
  .toast {{ position: fixed; bottom: 24px; right: 24px; background: #00f2fe; color: #000; padding: 14px 28px; border-radius: 100px; font-weight: 600; z-index: 9999; }}
 
529
  @keyframes fadeIn {{ from {{ opacity: 0; transform: translateY(-10px); }} to {{ opacity: 1; transform: translateY(0); }} }}
530
  </style>
531
  </head>
@@ -534,10 +601,10 @@ def home():
534
  <div class="header">
535
  <div class="header-top">
536
  <div class="title">
537
- <h1>OTP MULTI ACCOUNT · FOURSTORE</h1>
538
- <p>{CUSTOM_DOMAIN}</p>
539
  </div>
540
- <div class="status-badge">● ONLINE</div>
541
  </div>
542
  <div class="stats-grid">
543
  <div class="stat-card"><div class="stat-label">Total Akun</div><div class="stat-value">{len(accounts)}</div></div>
@@ -562,7 +629,7 @@ def home():
562
  <div class="search-box">
563
  <span class="search-icon">🔍</span>
564
  <form action="/" method="get" id="searchForm">
565
- <input type="text" class="search-input" name="q" placeholder="Cari OTP..." value="{request.args.get('q', '')}">
566
  </form>
567
  </div>
568
  <div class="filter-box">
@@ -575,8 +642,12 @@ def home():
575
  <span class="result-count">📊 {len(all_logs)} hasil</span>
576
  </div>
577
 
 
 
 
 
578
  <div class="otp-section">
579
- <h3 style="margin-bottom: 20px;">📨 OTP TERBARU <span style="background:#00f2fe20; padding:4px 12px; border-radius:100px; font-size:12px;">LIVE</span></h3>
580
  <table>
581
  <thead>
582
  <tr>
@@ -609,7 +680,9 @@ def home():
609
  }}
610
  }} catch(err) {{}}
611
  }};
612
- eventSource.onerror = function() {{ setTimeout(connectSSE, 3000); }};
 
 
613
  }}
614
 
615
  function updateFilter(key, value) {{
@@ -626,7 +699,7 @@ def home():
626
  navigator.clipboard.writeText(otp).then(() => {{
627
  const toast = document.createElement('div');
628
  toast.className = 'toast';
629
- toast.textContent = '✅ OTP copied!';
630
  document.body.appendChild(toast);
631
  setTimeout(() => toast.remove(), 2000);
632
  }});
@@ -639,6 +712,10 @@ def home():
639
  if (wibEl) wibEl.textContent = now.toISOString().substr(11, 8);
640
  }}
641
 
 
 
 
 
642
  connectSSE();
643
  setInterval(updateTime, 1000);
644
  </script>
@@ -649,16 +726,17 @@ def home():
649
 
650
  def generate_otp_rows(logs, search_query):
651
  if not logs:
652
- return '<tr><td colspan="7" class="empty-state">🔍 Belum ada OTP</td></tr>'
653
 
654
  rows = ""
655
- for log in logs[:100]:
656
  country = log.get('country', '')
657
  number = log.get('number', '')
658
  otp = log.get('otp', '')
659
  sms = log.get('sms', '')
660
  service = log.get('service', 'UNKNOWN')
661
  account = log.get('account_username', 'Unknown')
 
662
 
663
  if search_query:
664
  country = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', country, flags=re.I)
@@ -666,15 +744,18 @@ def generate_otp_rows(logs, search_query):
666
  otp = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', otp, flags=re.I)
667
 
668
  service_class = service.lower().replace(' ', '')
 
 
 
669
  rows += f'''
670
  <tr class="new-row">
671
- <td style="color:#00f2fe;">{log.get('time', '')}</td>
672
  <td><span class="account-badge">{account}</span></td>
673
  <td>{country}</td>
674
  <td><span class="number">{number}</span></td>
675
  <td><span class="service-badge {service_class}">{service}</span></td>
676
  <td><span class="otp-badge" onclick="copyOTP('{otp}')">{otp}</span></td>
677
- <td><div style="max-width:300px; overflow:hidden; text-overflow:ellipsis;" title="{log.get('sms', '')}">{log.get('sms', '')}</div></td>
678
  </tr>
679
  '''
680
  return rows
@@ -746,6 +827,10 @@ def stream():
746
  sse_clients.remove(q)
747
  return Response(generate(), mimetype="text/event-stream")
748
 
 
 
 
 
749
  def run_account_scraper(account_id):
750
  account = accounts.get(account_id)
751
  if not account:
@@ -760,7 +845,7 @@ def run_account_scraper(account_id):
760
  loop_count += 1
761
  try:
762
  print(f"\n{'='*60}")
763
- print(f"🔄 [{masked}] LOOP #{loop_count}")
764
  print(f"{'='*60}")
765
 
766
  if time.time() - account.get("last_cleanup", 0) > 300:
@@ -802,25 +887,31 @@ def run_account_scraper(account_id):
802
  if num_count > prev_num_count:
803
  print(f" 📱 Nomor: {mask_number(num)}")
804
  print(f" 📨 {prev_num_count} → {num_count} SMS")
805
- account["sms_counter"][key] = num_count
806
 
807
  all_sms = get_sms_fast(account_id, rng, num)
808
- new_sms = all_sms[prev_num_count:]
809
 
810
- for service, sms, otp in new_sms:
 
 
811
  if otp:
812
- sms_id = f"{rng}-{num}-{otp}"
813
- if sms_id not in account["sent_cache"]:
 
814
  masked_num = mask_number(num)
815
  msg = f"🔔 *NEW OTP*\n🌍 {country}\n📞 `{masked_num}`\n💬 {service}\n🔐 `{otp}`\n\n{sms[:300]}"
816
  print(f" 📤 Mengirim OTP {otp} ke Telegram...")
817
 
818
  if tg_send(account_id, msg):
819
- account["sent_cache"].append(sms_id)
820
- if len(account["sent_cache"]) > 1000:
821
- account["sent_cache"] = account["sent_cache"][-1000:]
822
- add_otp_log(account_id, country, masked_num, service, otp, sms)
823
- print(f" OTP: {otp} - {service} TERKIRIM!")
 
 
 
 
824
 
825
  time.sleep(0.5)
826
  else:
@@ -838,13 +929,13 @@ def run_account_scraper(account_id):
838
  def run_server():
839
  app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)
840
 
841
- # Jalankan server Flask
842
  Thread(target=run_server, daemon=True).start()
843
 
844
  def main():
845
- print("\n" + "="*60)
846
- print(" 🔥 OTP MULTI ACCOUNT - FOURSTORE")
847
- print(" ⚡ PORT: 7860")
 
848
  print(f" 🌐 DOMAIN: {CUSTOM_DOMAIN}")
849
  if mongo_client:
850
  print(" 📁 Data tersimpan di MongoDB")
@@ -853,10 +944,20 @@ def main():
853
  print(" 📋 LOGGING: FULL DETAIL")
854
  print(" 🔒 EMAIL SENSOR: AKTIF")
855
  print(" 🤖 AUTO LOGIN: AKTIF")
856
- print(" 📱 TELEGRAM: HANYA KIRIM OTP (TANPA MENU)")
857
- print("="*60 + "\n")
 
 
 
 
 
 
 
 
 
 
 
858
 
859
- # Auto login untuk akun yang sudah login sebelumnya
860
  for acc_id, acc in accounts.items():
861
  if acc.get("status"):
862
  masked = mask_email(acc['username'])
@@ -880,10 +981,19 @@ def main():
880
  else:
881
  save_accounts_to_file(accounts)
882
 
883
- print("\n BOT SIAP! Dashboard: https://fourstore-otp.hf.space")
 
 
 
 
884
 
885
  while True:
886
- time.sleep(60)
 
 
 
 
 
887
 
888
  if __name__ == "__main__":
889
  try:
 
85
  acc_copy = acc.copy()
86
  acc_copy.pop("session", None)
87
  acc_copy.pop("csrf", None)
88
+ if "otp_logs" in acc_copy and len(acc_copy["otp_logs"]) > 100:
89
+ acc_copy["otp_logs"] = acc_copy["otp_logs"][:100]
90
  acc_copy.pop("sms_cache", None)
91
  acc_copy.pop("sms_counter", None)
92
  acc_copy.pop("range_counter", None)
 
109
  data[acc_id]["session"] = None
110
  data[acc_id]["csrf"] = None
111
  data[acc_id]["status"] = False
112
+ if "otp_logs" not in data[acc_id]:
113
+ data[acc_id]["otp_logs"] = []
114
  data[acc_id]["sent_cache"] = []
115
  data[acc_id]["sms_cache"] = {}
116
  data[acc_id]["sms_counter"] = {}
 
129
  acc_copy = acc.copy()
130
  acc_copy.pop("session", None)
131
  acc_copy.pop("csrf", None)
132
+ if "otp_logs" in acc_copy and len(acc_copy["otp_logs"]) > 100:
133
+ acc_copy["otp_logs"] = acc_copy["otp_logs"][:100]
134
  acc_copy.pop("sms_cache", None)
135
  acc_copy.pop("sms_counter", None)
136
  acc_copy.pop("range_counter", None)
 
152
  app.secret_key = "fourstore-multi-account-secret"
153
  sse_clients = []
154
 
155
+ global_otp_logs = []
156
+
157
+ def get_utc_time():
158
+ return datetime.now(timezone.utc)
159
+
160
  def get_wib_time():
161
  return datetime.now(timezone.utc) + timedelta(hours=7)
162
 
163
+ def get_wib_time_str():
164
+ return get_wib_time().strftime("%H:%M:%S")
165
+
166
  def get_search_date():
167
+ now_utc = datetime.now(timezone.utc)
168
+ return now_utc.strftime("%Y-%m-%d")
169
 
170
  def login_account(account_id, username, password, bot_token, chat_id):
171
  try:
 
251
  if 'instagram' in s or 'ig' in s: return "INSTAGRAM"
252
  if 'tiktok' in s: return "TIKTOK"
253
  if 'temu' in s: return "TEMU"
254
+ if 'shopee' in s: return "SHOPEE"
255
+ if 'tokopedia' in s: return "TOKOPEDIA"
256
+ if 'grab' in s: return "GRAB"
257
+ if 'gojek' in s or 'go-jek' in s: return "GOJEK"
258
  return raw.upper()
259
 
260
  def extract_otp(text):
261
  if not text: return None
262
+ m = re.search(r"\b(\d{6})\b", text)
263
  if m:
264
+ return m.group(0)
265
+ m = re.search(r"\b(\d{4,5})\b", text)
266
  if m:
267
  return m.group(0)
268
+ m = re.search(r"\b(\d{3}[- ]?\d{3})\b", text)
269
+ if m:
270
+ return m.group(0).replace("-", "").replace(" ", "")
271
  digits = re.findall(r'\d+', text)
272
  for d in digits:
273
  if 4 <= len(d) <= 6:
274
  return d
275
  return None
276
 
277
+ def generate_otp_id(account_id, rng, number, otp, service):
278
+ return f"{account_id}-{rng}-{number}-{otp}-{service}"
279
+
280
  def get_ranges_with_count(account_id):
281
  account = accounts.get(account_id)
282
  if not account or not account.get("session") or not account.get("csrf"):
 
307
  })
308
 
309
  return ranges_data
310
+ except Exception as e:
311
+ print(f"Error get_ranges: {e}")
312
  return []
313
 
314
  def get_numbers_with_count(account_id, rng):
 
376
  })
377
 
378
  return numbers_data
379
+ except Exception as e:
380
+ print(f"Error get_numbers: {e}")
381
  return []
382
 
383
  def get_sms_fast(account_id, rng, number):
 
424
 
425
  account["sms_cache"][cache_key] = (time.time(), results)
426
  return results
427
+ except Exception as e:
428
+ print(f"Error get_sms: {e}")
429
  return []
430
 
431
+ def is_otp_sent(account_id, otp_id):
432
+ account = accounts.get(account_id)
433
+ if not account:
434
+ return True
435
+ return otp_id in account.get("sent_cache", [])
436
+
437
+ def mark_otp_sent(account_id, otp_id):
438
+ account = accounts.get(account_id)
439
+ if not account:
440
+ return
441
+
442
+ if "sent_cache" not in account:
443
+ account["sent_cache"] = []
444
+
445
+ account["sent_cache"].append(otp_id)
446
+ if len(account["sent_cache"]) > 1000:
447
+ account["sent_cache"] = account["sent_cache"][-1000:]
448
+
449
+ def add_otp_log(account_id, country, number, service, otp, sms, otp_id):
450
  account = accounts.get(account_id)
451
  if not account:
452
  return
453
 
454
  wib = get_wib_time()
455
+ wib_str = wib.strftime("%Y-%m-%d %H:%M:%S WIB")
456
+ time_only = wib.strftime("%H:%M:%S")
457
+
458
  log_entry = {
459
+ "time": time_only,
460
+ "time_full": wib_str,
461
+ "timestamp": time.time(),
462
  "country": country,
463
  "number": number,
464
  "service": service,
465
  "otp": otp,
466
+ "sms": sms[:150] + "..." if len(sms) > 150 else sms,
467
  "account_id": account_id,
468
+ "account_username": mask_email(account.get("username", "Unknown")),
469
+ "otp_id": otp_id
470
  }
471
 
472
  if "otp_logs" not in account:
 
476
  if len(account["otp_logs"]) > 100:
477
  account["otp_logs"] = account["otp_logs"][:100]
478
 
479
+ global_otp_logs.insert(0, log_entry)
480
+ if len(global_otp_logs) > 500:
481
+ global_otp_logs[:] = global_otp_logs[:500]
482
+
483
  broadcast_sse(log_entry)
484
+
485
+ if mongo_client:
486
+ save_accounts_to_mongodb(accounts)
487
+ else:
488
+ save_accounts_to_file(accounts)
489
+
490
+ print(f"✅ Log ditambahkan: {service} - {otp} - {time_only}")
491
  return log_entry
492
 
493
  def broadcast_sse(data):
 
503
 
504
  @app.route('/')
505
  def home():
506
+ all_logs = global_otp_logs.copy()
 
 
 
 
 
 
 
 
507
 
508
  search_query = request.args.get('q', '').lower()
509
  filter_service = request.args.get('service', '')
 
513
  search_query in log.get('country', '').lower() or
514
  search_query in log.get('number', '').lower() or
515
  search_query in log.get('otp', '').lower() or
516
+ search_query in log.get('sms', '').lower() or
517
+ search_query in log.get('account_username', '').lower()]
518
 
519
  if filter_service:
520
  all_logs = [log for log in all_logs if log.get('service') == filter_service]
521
 
522
  all_services = list(set([log.get('service') for log in all_logs if log.get('service')]))
523
 
524
+ total_otp = len(global_otp_logs)
525
+ today_otp = len([l for l in global_otp_logs if l.get('time_full', '').startswith(get_wib_time().strftime("%Y-%m-%d"))])
526
+
527
+ service_stats = {}
528
+ for log in global_otp_logs[:50]:
529
+ service = log.get('service', 'UNKNOWN')
530
+ service_stats[service] = service_stats.get(service, 0) + 1
531
 
532
  html = f"""
533
  <!DOCTYPE html>
 
568
  .filter-select {{ width: 100%; padding: 14px 20px; background: #1a1f2c; border: 1px solid #2d3540; border-radius: 100px; color: #e4e6eb; font-size: 15px; cursor: pointer; }}
569
  .clear-btn {{ padding: 8px 16px; background: #2d3a4a; border: none; border-radius: 100px; color: white; font-size: 13px; cursor: pointer; text-decoration: none; }}
570
 
571
+ .stats-mini {{ display: flex; gap: 12px; flex-wrap: wrap; margin: 10px 0; }}
572
+ .service-tag {{ background: #1a1f2c; padding: 6px 14px; border-radius: 100px; font-size: 12px; border: 1px solid #2d3540; }}
573
+
574
  .otp-section {{ background: #0f131c; border-radius: 24px; padding: 28px; border: 1px solid #2d3540; overflow-x: auto; }}
575
+ table {{ width: 100%; border-collapse: collapse; min-width: 1100px; }}
576
  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; }}
577
  td {{ padding: 16px 12px; border-bottom: 1px solid #262c38; font-size: 14px; }}
578
  .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; }}
579
+ .service-badge {{ padding: 6px 14px; border-radius: 100px; font-size: 12px; font-weight: 600; display: inline-block; }}
580
  .whatsapp {{ background: #25D36620; color: #25D366; border: 1px solid #25D36640; }}
581
  .telegram {{ background: #26A5E420; color: #26A5E4; border: 1px solid #26A5E440; }}
582
+ .google {{ background: #4285F420; color: #4285F4; border: 1px solid #4285F440; }}
583
+ .facebook {{ background: #1877F220; color: #1877F2; border: 1px solid #1877F240; }}
584
+ .instagram {{ background: #E4405F20; color: #E4405F; border: 1px solid #E4405F40; }}
585
+ .tiktok {{ background: #00000020; color: #FFFFFF; border: 1px solid #FFFFFF40; }}
586
+ .shopee {{ background: #EE4D2D20; color: #EE4D2D; border: 1px solid #EE4D2D40; }}
587
+ .tokopedia {{ background: #42B54920; color: #42B549; border: 1px solid #42B54940; }}
588
+ .grab {{ background: #00B14F20; color: #00B14F; border: 1px solid #00B14F40; }}
589
+ .gojek {{ background: #00880A20; color: #00880A; border: 1px solid #00880A40; }}
590
  .number {{ font-family: monospace; }}
591
  .empty-state {{ text-align: center; padding: 60px; color: #8b949e; }}
592
  .highlight {{ background: #00f2fe30; border-radius: 4px; padding: 0 2px; }}
593
  .new-row {{ animation: fadeIn 0.3s ease; background: linear-gradient(90deg, #00f2fe10, transparent); }}
594
  .toast {{ position: fixed; bottom: 24px; right: 24px; background: #00f2fe; color: #000; padding: 14px 28px; border-radius: 100px; font-weight: 600; z-index: 9999; }}
595
+ .account-badge {{ background: #2d3a4a; padding: 4px 12px; border-radius: 100px; font-size: 12px; }}
596
  @keyframes fadeIn {{ from {{ opacity: 0; transform: translateY(-10px); }} to {{ opacity: 1; transform: translateY(0); }} }}
597
  </style>
598
  </head>
 
601
  <div class="header">
602
  <div class="header-top">
603
  <div class="title">
604
+ <h1>📱 OTP MULTI ACCOUNT · FOURSTORE</h1>
605
+ <p>{CUSTOM_DOMAIN} · {get_wib_time().strftime('%d %B %Y')}</p>
606
  </div>
607
+ <div class="status-badge">● ONLINE · 24 JAM</div>
608
  </div>
609
  <div class="stats-grid">
610
  <div class="stat-card"><div class="stat-label">Total Akun</div><div class="stat-value">{len(accounts)}</div></div>
 
629
  <div class="search-box">
630
  <span class="search-icon">🔍</span>
631
  <form action="/" method="get" id="searchForm">
632
+ <input type="text" class="search-input" name="q" placeholder="Cari country, nomor, OTP, akun..." value="{request.args.get('q', '')}">
633
  </form>
634
  </div>
635
  <div class="filter-box">
 
642
  <span class="result-count">📊 {len(all_logs)} hasil</span>
643
  </div>
644
 
645
+ <div class="stats-mini">
646
+ {''.join([f'<span class="service-tag">{service}: {count}</span>' for service, count in sorted(service_stats.items())][:8])}
647
+ </div>
648
+
649
  <div class="otp-section">
650
+ <h3 style="margin-bottom: 20px;">📨 OTP TERBARU <span style="background:#00f2fe20; padding:4px 12px; border-radius:100px; font-size:12px;">LIVE · 24 JAM</span></h3>
651
  <table>
652
  <thead>
653
  <tr>
 
680
  }}
681
  }} catch(err) {{}}
682
  }};
683
+ eventSource.onerror = function() {{
684
+ setTimeout(connectSSE, 3000);
685
+ }};
686
  }}
687
 
688
  function updateFilter(key, value) {{
 
699
  navigator.clipboard.writeText(otp).then(() => {{
700
  const toast = document.createElement('div');
701
  toast.className = 'toast';
702
+ toast.textContent = '✅ OTP copied: ' + otp;
703
  document.body.appendChild(toast);
704
  setTimeout(() => toast.remove(), 2000);
705
  }});
 
712
  if (wibEl) wibEl.textContent = now.toISOString().substr(11, 8);
713
  }}
714
 
715
+ setInterval(() => {{
716
+ location.reload();
717
+ }}, 30000);
718
+
719
  connectSSE();
720
  setInterval(updateTime, 1000);
721
  </script>
 
726
 
727
  def generate_otp_rows(logs, search_query):
728
  if not logs:
729
+ return '<tr><td colspan="7" class="empty-state">📭 Belum ada OTP · Menunggu OTP masuk...</td></tr>'
730
 
731
  rows = ""
732
+ for log in logs[:50]:
733
  country = log.get('country', '')
734
  number = log.get('number', '')
735
  otp = log.get('otp', '')
736
  sms = log.get('sms', '')
737
  service = log.get('service', 'UNKNOWN')
738
  account = log.get('account_username', 'Unknown')
739
+ time_full = log.get('time_full', '')
740
 
741
  if search_query:
742
  country = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', country, flags=re.I)
 
744
  otp = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', otp, flags=re.I)
745
 
746
  service_class = service.lower().replace(' ', '')
747
+
748
+ time_tooltip = f' title="{time_full}"'
749
+
750
  rows += f'''
751
  <tr class="new-row">
752
+ <td{time_tooltip} style="color:#00f2fe;">{log.get('time', '')}</td>
753
  <td><span class="account-badge">{account}</span></td>
754
  <td>{country}</td>
755
  <td><span class="number">{number}</span></td>
756
  <td><span class="service-badge {service_class}">{service}</span></td>
757
  <td><span class="otp-badge" onclick="copyOTP('{otp}')">{otp}</span></td>
758
+ <td><div style="max-width:350px; overflow:hidden; text-overflow:ellipsis;" title="{log.get('sms', '')}">{log.get('sms', '')}</div></td>
759
  </tr>
760
  '''
761
  return rows
 
827
  sse_clients.remove(q)
828
  return Response(generate(), mimetype="text/event-stream")
829
 
830
+ @app.route('/api/logs')
831
+ def api_logs():
832
+ return json.dumps(global_otp_logs[:100])
833
+
834
  def run_account_scraper(account_id):
835
  account = accounts.get(account_id)
836
  if not account:
 
845
  loop_count += 1
846
  try:
847
  print(f"\n{'='*60}")
848
+ print(f"🔄 [{masked}] LOOP #{loop_count} - {get_wib_time_str()}")
849
  print(f"{'='*60}")
850
 
851
  if time.time() - account.get("last_cleanup", 0) > 300:
 
887
  if num_count > prev_num_count:
888
  print(f" 📱 Nomor: {mask_number(num)}")
889
  print(f" 📨 {prev_num_count} → {num_count} SMS")
 
890
 
891
  all_sms = get_sms_fast(account_id, rng, num)
892
+ print(f" 📨 Total SMS ditemukan: {len(all_sms)}")
893
 
894
+ for i in range(prev_num_count, len(all_sms)):
895
+ service, sms, otp = all_sms[i]
896
+
897
  if otp:
898
+ otp_id = generate_otp_id(account_id, rng, num, otp, service)
899
+
900
+ if not is_otp_sent(account_id, otp_id):
901
  masked_num = mask_number(num)
902
  msg = f"🔔 *NEW OTP*\n🌍 {country}\n📞 `{masked_num}`\n💬 {service}\n🔐 `{otp}`\n\n{sms[:300]}"
903
  print(f" 📤 Mengirim OTP {otp} ke Telegram...")
904
 
905
  if tg_send(account_id, msg):
906
+ mark_otp_sent(account_id, otp_id)
907
+ add_otp_log(account_id, country, masked_num, service, otp, sms, otp_id)
908
+ print(f" OTP: {otp} - {service} TERKIRIM! (ID: {otp_id})")
909
+ else:
910
+ print(f" Gagal mengirim OTP {otp}")
911
+ else:
912
+ print(f" ⏭️ OTP {otp} sudah pernah dikirim sebelumnya (ID: {otp_id})")
913
+
914
+ account["sms_counter"][key] = num_count
915
 
916
  time.sleep(0.5)
917
  else:
 
929
  def run_server():
930
  app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)
931
 
 
932
  Thread(target=run_server, daemon=True).start()
933
 
934
  def main():
935
+ print("\n" + "="*70)
936
+ print(" 🔥 OTP MULTI ACCOUNT - FOURSTORE 🔥")
937
+ print("="*70)
938
+ print(f" ⚡ PORT: 7860")
939
  print(f" 🌐 DOMAIN: {CUSTOM_DOMAIN}")
940
  if mongo_client:
941
  print(" 📁 Data tersimpan di MongoDB")
 
944
  print(" 📋 LOGGING: FULL DETAIL")
945
  print(" 🔒 EMAIL SENSOR: AKTIF")
946
  print(" 🤖 AUTO LOGIN: AKTIF")
947
+ print(" 📱 TELEGRAM: HANYA KIRIM OTP")
948
+ print(" 🛡️ ANTI DUPLIKAT: AKTIF")
949
+ print(" 🌍 SERVER UTC: AKTIF")
950
+ print(" ⏰ 24 JAM MONITORING: AKTIF")
951
+ print("="*70 + "\n")
952
+
953
+ for acc_id, acc in accounts.items():
954
+ if "otp_logs" in acc and acc["otp_logs"]:
955
+ for log in acc["otp_logs"]:
956
+ global_otp_logs.append(log)
957
+
958
+ global_otp_logs.sort(key=lambda x: x.get('timestamp', 0), reverse=True)
959
+ print(f"📊 Total logs dimuat: {len(global_otp_logs)}")
960
 
 
961
  for acc_id, acc in accounts.items():
962
  if acc.get("status"):
963
  masked = mask_email(acc['username'])
 
981
  else:
982
  save_accounts_to_file(accounts)
983
 
984
+ print("\n" + "="*70)
985
+ print("✅ BOT SIAP! Dashboard: https://fourstore-otp.hf.space")
986
+ print("🌍 SERVER MENGGUNAKAN UTC - SEARCH DATE: UTC HARI INI")
987
+ print("⏰ 24 JAM AKTIF - SEMUA OTP AKAN MUNCUL DI WEB")
988
+ print("="*70 + "\n")
989
 
990
  while True:
991
+ time.sleep(300)
992
+ if mongo_client:
993
+ save_accounts_to_mongodb(accounts)
994
+ else:
995
+ save_accounts_to_file(accounts)
996
+ print(f"💾 Auto-save data - {get_wib_time_str()}")
997
 
998
  if __name__ == "__main__":
999
  try: