Fourstore commited on
Commit
2fa7464
·
verified ·
1 Parent(s): d071f5c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +50 -262
app.py CHANGED
@@ -15,13 +15,13 @@ from pymongo import MongoClient
15
  from pymongo.errors import ConnectionFailure
16
  from functools import wraps
17
 
18
- MONGO_URI = os.environ.get("MONGGODB_URI")
19
  DB_NAME = "otp_bot"
20
  COLLECTION_NAME = "accounts"
21
 
22
- if MONGO_URI:
23
  try:
24
- mongo_client = MongoClient(MONGO_URI)
25
  db = mongo_client[DB_NAME]
26
  accounts_collection = db[COLLECTION_NAME]
27
  mongo_client.admin.command('ping')
@@ -30,7 +30,7 @@ if MONGO_URI:
30
  print(f"❌ MongoDB Connection Failed: {e}")
31
  mongo_client = None
32
  else:
33
- print("⚠️ MONGO_URI tidak di set, menggunakan penyimpanan lokal")
34
  mongo_client = None
35
 
36
  print = lambda *args, **kwargs: __builtins__.print(*args, **kwargs, flush=True)
@@ -152,15 +152,6 @@ app = Flask('')
152
  app.secret_key = "fourstore-multi-account-secret"
153
  sse_clients = []
154
 
155
- def owner_required(f):
156
- @wraps(f)
157
- def decorated_function(*args, **kwargs):
158
- owner_id = flask_session.get('owner_id')
159
- if not owner_id:
160
- return redirect('/login_owner')
161
- return f(*args, **kwargs)
162
- return decorated_function
163
-
164
  def get_wib_time():
165
  return datetime.now(timezone.utc) + timedelta(hours=7)
166
 
@@ -453,55 +444,10 @@ def broadcast_sse(data):
453
  for q in dead:
454
  sse_clients.remove(q)
455
 
456
- @app.route('/login_owner', methods=['GET', 'POST'])
457
- def login_owner():
458
- if request.method == 'POST':
459
- owner_id = request.form.get('owner_id')
460
- if owner_id:
461
- flask_session['owner_id'] = owner_id
462
- return redirect('/')
463
- return '''
464
- <!DOCTYPE html>
465
- <html>
466
- <head>
467
- <title>Owner Login</title>
468
- <style>
469
- body { background: #0a0c10; color: #e4e6eb; font-family: Inter, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
470
- .login-box { background: #1a1f2c; padding: 40px; border-radius: 24px; border: 1px solid #2d3540; width: 300px; }
471
- h2 { color: #00f2fe; margin-bottom: 20px; }
472
- input { width: 100%; padding: 12px; background: #0a0c10; border: 1px solid #2d3540; border-radius: 12px; color: #e4e6eb; margin-bottom: 20px; }
473
- button { background: #00f2fe; color: #0a0c10; border: none; padding: 12px; border-radius: 12px; font-weight: 600; cursor: pointer; width: 100%; }
474
- </style>
475
- </head>
476
- <body>
477
- <div class="login-box">
478
- <h2>Owner Login</h2>
479
- <form method="post">
480
- <input type="text" name="owner_id" placeholder="Masukkan Owner ID" required>
481
- <button type="submit">Login</button>
482
- </form>
483
- </div>
484
- </body>
485
- </html>
486
- '''
487
-
488
- @app.route('/logout_owner')
489
- def logout_owner():
490
- flask_session.pop('owner_id', None)
491
- return redirect('/')
492
-
493
  @app.route('/')
494
- @owner_required
495
  def home():
496
- owner_id = flask_session.get('owner_id')
497
-
498
- user_accounts = {}
499
- for acc_id, acc in accounts.items():
500
- if acc.get("owner_id") == owner_id:
501
- user_accounts[acc_id] = acc
502
-
503
  all_logs = []
504
- for acc_id, acc in user_accounts.items():
505
  if acc.get("otp_logs"):
506
  for log in acc["otp_logs"]:
507
  log_copy = log.copy()
@@ -529,15 +475,16 @@ def home():
529
 
530
  all_services = list(set([log.get('service') for log in all_logs if log.get('service')]))
531
 
532
- total_otp = sum(len(acc.get('sent_cache', [])) for acc in user_accounts.values())
533
  today_otp = len(all_logs)
534
 
535
  accounts_list = []
536
- for acc_id, acc in user_accounts.items():
537
  accounts_list.append({
538
  "id": acc_id,
539
  "username": mask_email(acc.get("username", "Unknown")),
540
  "status": acc.get("status", False),
 
541
  "bot_token": "✅" if acc.get("bot_token") else "❌",
542
  "chat_id": acc.get("chat_id", "-")[:5] + "..." if acc.get("chat_id") and len(acc.get("chat_id")) > 5 else acc.get("chat_id", "-")
543
  })
@@ -561,7 +508,6 @@ def home():
561
  .nav-item {{ color: #8b949e; text-decoration: none; padding: 8px 16px; border-radius: 8px; transition: all 0.2s; }}
562
  .nav-item:hover {{ background: #2d3540; color: #00f2fe; }}
563
  .nav-item.active {{ background: #00f2fe20; color: #00f2fe; border: 1px solid #00f2fe40; }}
564
- .owner-badge {{ background: #4a3a2d; padding: 8px 16px; border-radius: 100px; font-size: 14px; }}
565
 
566
  .header {{ background: linear-gradient(145deg, #1a1f2c, #0f131c); border-radius: 24px; padding: 28px; margin-bottom: 28px; border: 1px solid #2d3540; }}
567
  .header-top {{ display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 15px; }}
@@ -583,7 +529,7 @@ def home():
583
  .btn-danger {{ background: #ff4d4d; color: white; }}
584
  .btn-small {{ padding: 6px 12px; font-size: 12px; }}
585
 
586
- .account-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 30px; }}
587
  .account-card {{ background: #1a1f2c; border-radius: 16px; padding: 20px; border: 1px solid #2d3540; position: relative; }}
588
  .account-card.online {{ border-color: #00f2fe; box-shadow: 0 0 15px #00f2fe20; }}
589
  .account-status {{ position: absolute; top: 20px; right: 20px; width: 12px; height: 12px; border-radius: 50%; }}
@@ -600,12 +546,13 @@ def home():
600
  .clear-btn {{ padding: 8px 16px; background: #2d3a4a; border: none; border-radius: 100px; color: white; font-size: 13px; cursor: pointer; text-decoration: none; }}
601
 
602
  .otp-section {{ background: #0f131c; border-radius: 24px; padding: 28px; border: 1px solid #2d3540; overflow-x: auto; }}
603
- table {{ width: 100%; border-collapse: collapse; min-width: 1100px; }}
604
  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; }}
605
  td {{ padding: 16px 12px; border-bottom: 1px solid #262c38; font-size: 14px; }}
606
  .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; }}
607
  .service-badge {{ background: #2d3a4a; padding: 6px 14px; border-radius: 100px; font-size: 12px; font-weight: 600; display: inline-block; }}
608
  .account-badge {{ background: #4a3a2d; padding: 4px 10px; border-radius: 20px; font-size: 11px; display: inline-block; }}
 
609
  .whatsapp {{ background: #25D36620; color: #25D366; border: 1px solid #25D36640; }}
610
  .telegram {{ background: #26A5E420; color: #26A5E4; border: 1px solid #26A5E440; }}
611
  .number {{ font-family: monospace; }}
@@ -622,9 +569,6 @@ def home():
622
  <div class="nav-brand">OTP MULTI ACCOUNT</div>
623
  <div class="nav-menu">
624
  <a href="/" class="nav-item active">🏠 Dashboard</a>
625
- <a href="/accounts" class="nav-item">📋 My Accounts</a>
626
- <a href="/logout_owner" class="nav-item">🚪 Logout</a>
627
- <span class="owner-badge">👤 Owner: {owner_id}</span>
628
  </div>
629
  </div>
630
 
@@ -637,8 +581,8 @@ def home():
637
  <div class="status-badge">● ONLINE</div>
638
  </div>
639
  <div class="stats-grid">
640
- <div class="stat-card"><div class="stat-label">Total Akun</div><div class="stat-value">{len(user_accounts)}</div></div>
641
- <div class="stat-card"><div class="stat-label">Akun Online</div><div class="stat-value">{sum(1 for a in user_accounts.values() if a.get('status'))}</div></div>
642
  <div class="stat-card"><div class="stat-label">Total OTP</div><div class="stat-value">{total_otp}</div></div>
643
  <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>
644
  </div>
@@ -651,17 +595,14 @@ def home():
651
  <input type="password" name="password" placeholder="Password" class="form-input" required>
652
  <input type="text" name="bot_token" placeholder="Bot Token (opsional)" class="form-input">
653
  <input type="text" name="chat_id" placeholder="Chat ID (opsional)" class="form-input">
654
- <input type="hidden" name="owner_id" value="{owner_id}">
655
  <button type="submit" class="btn">Tambah & Login Otomatis</button>
656
  </form>
657
  </div>
658
 
659
- <div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
660
- <h3>📋 Akun Terbaru</h3>
661
- <a href="/accounts" class="btn btn-small">Lihat Semua Akun →</a>
662
- </div>
663
  <div class="account-grid">
664
- {generate_preview_accounts(accounts_list[:3])}
665
  </div>
666
 
667
  <div class="search-section">
@@ -677,12 +618,6 @@ def home():
677
  {''.join([f'<option value="{s}" {"selected" if filter_service == s else ""}>📱 {s}</option>' for s in sorted(all_services)])}
678
  </select>
679
  </div>
680
- <div class="filter-box">
681
- <select class="filter-select" name="account" onchange="updateFilter('account', this.value)">
682
- <option value="">👤 Semua Akun</option>
683
- {''.join([f'<option value="{a["id"]}" {"selected" if filter_account == a["id"] else ""}>👤 {a["username"]}</option>' for a in accounts_list[:10]])}
684
- </select>
685
- </div>
686
  <a href="/" class="clear-btn">✕ Reset</a>
687
  <span class="result-count">📊 {len(all_logs)} hasil</span>
688
  </div>
@@ -694,6 +629,7 @@ def home():
694
  <tr>
695
  <th>WIB</th>
696
  <th>Akun</th>
 
697
  <th>Country</th>
698
  <th>Number</th>
699
  <th>Service</th>
@@ -744,6 +680,13 @@ def home():
744
  .then(() => location.reload());
745
  }}
746
 
 
 
 
 
 
 
 
747
  function copyOTP(otp) {{
748
  navigator.clipboard.writeText(otp).then(() => {{
749
  const toast = document.createElement('div');
@@ -769,7 +712,7 @@ def home():
769
  """
770
  return html
771
 
772
- def generate_preview_accounts(accounts_list):
773
  if not accounts_list:
774
  return '<div style="grid-column:1/-1; text-align:center; padding:40px; color:#8b949e;">Belum ada akun. Tambah akun di atas!</div>'
775
 
@@ -780,166 +723,25 @@ def generate_preview_accounts(accounts_list):
780
  <div class="account-card {status_class}">
781
  <div class="account-status status-{status_class}"></div>
782
  <h4>{acc['username']}</h4>
783
- <p style="margin-top:8px; color:#8b949e; font-size:12px;">Bot: {acc['bot_token']} | Chat: {acc['chat_id']}</p>
 
 
 
 
 
784
  <p style="color:#8b949e; font-size:12px;">Status: {'🟢 Online' if acc['status'] else '🔴 Offline'}</p>
785
- </div>
786
- """
787
- return html
788
-
789
- @app.route('/accounts')
790
- @owner_required
791
- def accounts_page():
792
- owner_id = flask_session.get('owner_id')
793
-
794
- user_accounts = {}
795
- for acc_id, acc in accounts.items():
796
- if acc.get("owner_id") == owner_id:
797
- user_accounts[acc_id] = acc
798
-
799
- accounts_list = []
800
- for acc_id, acc in user_accounts.items():
801
- accounts_list.append({
802
- "id": acc_id,
803
- "username": mask_email(acc.get("username", "Unknown")),
804
- "status": acc.get("status", False),
805
- "bot_token": acc.get("bot_token", "-")[:10] + "..." if len(acc.get("bot_token", "")) > 10 else acc.get("bot_token", "-"),
806
- "chat_id": acc.get("chat_id", "-"),
807
- "created_at": datetime.fromtimestamp(acc.get("created_at", time.time())).strftime("%Y-%m-%d %H:%M") if acc.get("created_at") else "-",
808
- "last_login": datetime.fromtimestamp(acc.get("last_login", 0)).strftime("%Y-%m-%d %H:%M") if acc.get("last_login") else "-",
809
- "otp_count": len(acc.get("sent_cache", []))
810
- })
811
-
812
- html = f"""
813
- <!DOCTYPE html>
814
- <html lang="en">
815
- <head>
816
- <meta charset="UTF-8">
817
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
818
- <title>My Accounts · FOURSTORE</title>
819
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
820
- <style>
821
- * {{ margin: 0; padding: 0; box-sizing: border-box; }}
822
- body {{ font-family: 'Inter', sans-serif; background: #0a0c10; color: #e4e6eb; padding: 24px; }}
823
- .container {{ max-width: 1400px; margin: 0 auto; }}
824
-
825
- .navbar {{ background: #1a1f2c; border-radius: 16px; padding: 16px 24px; margin-bottom: 24px; display: flex; justify-content: space-between; align-items: center; border: 1px solid #2d3540; }}
826
- .nav-brand {{ font-size: 20px; font-weight: 700; color: #00f2fe; }}
827
- .nav-menu {{ display: flex; gap: 20px; align-items: center; }}
828
- .nav-item {{ color: #8b949e; text-decoration: none; padding: 8px 16px; border-radius: 8px; }}
829
- .nav-item:hover {{ background: #2d3540; color: #00f2fe; }}
830
- .nav-item.active {{ background: #00f2fe20; color: #00f2fe; border: 1px solid #00f2fe40; }}
831
- .owner-badge {{ background: #4a3a2d; padding: 8px 16px; border-radius: 100px; font-size: 14px; }}
832
-
833
- .header {{ background: linear-gradient(145deg, #1a1f2c, #0f131c); border-radius: 24px; padding: 28px; margin-bottom: 28px; border: 1px solid #2d3540; }}
834
- .header-top {{ display: flex; justify-content: space-between; align-items: center; }}
835
- .title h1 {{ font-size: 28px; font-weight: 700; background: linear-gradient(135deg, #00f2fe, #4facfe); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }}
836
-
837
- .accounts-table {{ background: #0f131c; border-radius: 24px; padding: 28px; border: 1px solid #2d3540; overflow-x: auto; }}
838
- table {{ width: 100%; border-collapse: collapse; min-width: 1000px; }}
839
- 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; }}
840
- td {{ padding: 16px 12px; border-bottom: 1px solid #262c38; font-size: 14px; }}
841
- .status-badge {{ display: inline-block; padding: 4px 8px; border-radius: 100px; font-size: 12px; font-weight: 600; }}
842
- .status-online {{ background: #0a4d3c; color: #a0f0d0; }}
843
- .status-offline {{ background: #4a2c2c; color: #ffb3b3; }}
844
- .btn {{ background: #00f2fe; color: #0a0c10; border: none; padding: 6px 12px; border-radius: 8px; font-size: 12px; font-weight: 600; cursor: pointer; text-decoration: none; display: inline-block; }}
845
- .btn-danger {{ background: #ff4d4d; color: white; }}
846
- .btn-small {{ padding: 4px 8px; font-size: 11px; }}
847
- .action-group {{ display: flex; gap: 5px; }}
848
- </style>
849
- </head>
850
- <body>
851
- <div class="container">
852
- <div class="navbar">
853
- <div class="nav-brand">OTP MULTI ACCOUNT</div>
854
- <div class="nav-menu">
855
- <a href="/" class="nav-item">🏠 Dashboard</a>
856
- <a href="/accounts" class="nav-item active">📋 My Accounts</a>
857
- <a href="/logout_owner" class="nav-item">🚪 Logout</a>
858
- <span class="owner-badge">👤 Owner: {owner_id}</span>
859
- </div>
860
- </div>
861
-
862
- <div class="header">
863
- <div class="header-top">
864
- <div class="title">
865
- <h1>📋 My Accounts</h1>
866
- <p style="color:#8b949e; margin-top:8px;">Total: {len(accounts_list)} akun | Online: {sum(1 for a in accounts_list if a['status'])}</p>
867
- </div>
868
- <a href="/" class="btn">+ Tambah Akun Baru</a>
869
- </div>
870
- </div>
871
-
872
- <div class="accounts-table">
873
- <table>
874
- <thead>
875
- <tr>
876
- <th>No</th>
877
- <th>Username</th>
878
- <th>Status</th>
879
- <th>Bot Token</th>
880
- <th>Chat ID</th>
881
- <th>OTP Count</th>
882
- <th>Created At</th>
883
- <th>Last Login</th>
884
- <th>Actions</th>
885
- </tr>
886
- </thead>
887
- <tbody>
888
- {generate_accounts_rows(accounts_list)}
889
- </tbody>
890
- </table>
891
  </div>
892
  </div>
893
-
894
- <script>
895
- function loginAccount(accountId) {{
896
- fetch('/login_account/' + accountId, {{method: 'POST'}})
897
- .then(() => location.reload());
898
- }}
899
-
900
- function logoutAccount(accountId) {{
901
- fetch('/logout_account/' + accountId, {{method: 'POST'}})
902
- .then(() => location.reload());
903
- }}
904
- </script>
905
- </body>
906
- </html>
907
- """
908
- return html
909
-
910
- def generate_accounts_rows(accounts_list):
911
- if not accounts_list:
912
- return '<tr><td colspan="9" style="text-align:center; padding:60px; color:#8b949e;">Belum ada akun. Tambah akun di dashboard!</td></tr>'
913
-
914
- rows = ""
915
- for i, acc in enumerate(accounts_list, 1):
916
- status_class = "status-online" if acc["status"] else "status-offline"
917
- status_text = "🟢 Online" if acc["status"] else "🔴 Offline"
918
-
919
- rows += f"""
920
- <tr>
921
- <td>{i}</td>
922
- <td>{acc['username']}</td>
923
- <td><span class="status-badge {status_class}">{status_text}</span></td>
924
- <td>{acc['bot_token']}</td>
925
- <td>{acc['chat_id']}</td>
926
- <td>{acc['otp_count']}</td>
927
- <td>{acc['created_at']}</td>
928
- <td>{acc['last_login']}</td>
929
- <td>
930
- <div class="action-group">
931
- <button onclick="loginAccount('{acc['id']}')" class="btn btn-small">Login</button>
932
- <button onclick="logoutAccount('{acc['id']}')" class="btn btn-small btn-danger">Logout</button>
933
- <a href="/delete_account/{acc['id']}" class="btn btn-small btn-danger" onclick="return confirm('Hapus akun ini?')">Hapus</a>
934
- </div>
935
- </td>
936
- </tr>
937
  """
938
- return rows
939
 
940
  def generate_otp_rows(logs, search_query):
941
  if not logs:
942
- return '<tr><td colspan="7" class="empty-state">🔍 Belum ada OTP</td></tr>'
943
 
944
  rows = ""
945
  for log in logs[:100]:
@@ -949,6 +751,8 @@ def generate_otp_rows(logs, search_query):
949
  sms = log.get('sms', '')
950
  service = log.get('service', 'UNKNOWN')
951
  account = log.get('account_username', 'Unknown')
 
 
952
 
953
  if search_query:
954
  country = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', country, flags=re.I)
@@ -960,6 +764,7 @@ def generate_otp_rows(logs, search_query):
960
  <tr class="new-row">
961
  <td style="color:#00f2fe;">{log.get('time', '')}</td>
962
  <td><span class="account-badge">{account}</span></td>
 
963
  <td>{country}</td>
964
  <td><span class="number">{number}</span></td>
965
  <td><span class="service-badge {service_class}">{service}</span></td>
@@ -976,10 +781,10 @@ def add_account_route():
976
  password = request.form['password']
977
  bot_token = request.form.get('bot_token', '')
978
  chat_id = request.form.get('chat_id', '')
979
- owner_id = request.form.get('owner_id')
980
 
981
  if not owner_id:
982
- owner_id = flask_session.get('owner_id')
983
 
984
  masked = mask_email(username)
985
  print(f"\n➕ TAMBAH AKUN BARU: {masked} (ID: {account_id}) (Owner: {owner_id})")
@@ -1030,14 +835,8 @@ def add_account_route():
1030
  return redirect('/')
1031
 
1032
  @app.route('/delete_account/<account_id>')
1033
- @owner_required
1034
  def delete_account_route(account_id):
1035
- owner_id = flask_session.get('owner_id')
1036
-
1037
  if account_id in accounts:
1038
- if accounts[account_id].get("owner_id") != owner_id:
1039
- return "Unauthorized", 403
1040
-
1041
  username = accounts[account_id].get('username', 'Unknown')
1042
  masked = mask_email(username)
1043
  print(f"\n🗑️ HAPUS AKUN: {masked} (ID: {account_id})")
@@ -1049,18 +848,13 @@ def delete_account_route(account_id):
1049
  save_accounts_to_file(accounts)
1050
  print(f"✅ Akun dihapus")
1051
 
1052
- return redirect(request.referrer or '/')
1053
 
1054
  @app.route('/login_account/<account_id>', methods=['POST'])
1055
- @owner_required
1056
  def login_account_route(account_id):
1057
- owner_id = flask_session.get('owner_id')
1058
  print(f"\n🔔🔔🔔 LOGIN ACCOUNT DIPANGGIL: {account_id} 🔔🔔🔔")
1059
 
1060
  if account_id in accounts:
1061
- if accounts[account_id].get("owner_id") != owner_id:
1062
- return "Unauthorized", 403
1063
-
1064
  acc = accounts[account_id]
1065
  masked = mask_email(acc.get('username', 'Unknown'))
1066
  print(f"📋 Data akun: {masked}")
@@ -1072,7 +866,7 @@ def login_account_route(account_id):
1072
  acc['password'],
1073
  acc.get('bot_token', ''),
1074
  acc.get('chat_id', ''),
1075
- owner_id
1076
  )
1077
 
1078
  print(f"📝 Result: {success} - {msg}")
@@ -1087,17 +881,11 @@ def login_account_route(account_id):
1087
  else:
1088
  print(f"❌ Account ID {account_id} tidak ditemukan!")
1089
 
1090
- return redirect(request.referrer or '/')
1091
 
1092
  @app.route('/logout_account/<account_id>', methods=['POST'])
1093
- @owner_required
1094
  def logout_account_route(account_id):
1095
- owner_id = flask_session.get('owner_id')
1096
-
1097
  if account_id in accounts:
1098
- if accounts[account_id].get("owner_id") != owner_id:
1099
- return "Unauthorized", 403
1100
-
1101
  username = accounts[account_id].get('username', 'Unknown')
1102
  masked = mask_email(username)
1103
  print(f"\n🔌 LOGOUT: {masked} (ID: {account_id})")
@@ -1111,7 +899,7 @@ def logout_account_route(account_id):
1111
  save_accounts_to_file(accounts)
1112
  print(f"✅ Logout berhasil")
1113
 
1114
- return redirect(request.referrer or '/')
1115
 
1116
  @app.route('/stream')
1117
  def stream():
@@ -1231,8 +1019,8 @@ def main():
1231
  print(" 📁 Data tersimpan di file accounts.json")
1232
  print(" 📋 LOGGING: FULL DETAIL")
1233
  print(" 🔒 EMAIL SENSOR: AKTIF")
1234
- print(" 🤖 AUTO LOGIN: AKTIF (setelah tambah akun)")
1235
- print(" 👑 OWNER SYSTEM: AKTIF (owner_id di input form)")
1236
  print("="*60 + "\n")
1237
 
1238
  for acc_id, acc in accounts.items():
@@ -1245,7 +1033,7 @@ def main():
1245
  acc['password'],
1246
  acc.get('bot_token', ''),
1247
  acc.get('chat_id', ''),
1248
- acc.get('owner_id', 'unknown')
1249
  )
1250
  if success:
1251
  thread = Thread(target=run_account_scraper, args=(acc_id,), daemon=True)
@@ -1260,7 +1048,7 @@ def main():
1260
  save_accounts_to_file(accounts)
1261
 
1262
  print("\n✅ BOT SIAP! Dashboard: https://fourstore-otp.hf.space")
1263
- print("🔐 Login dengan Owner ID untuk mengakses")
1264
 
1265
  while True:
1266
  time.sleep(60)
 
15
  from pymongo.errors import ConnectionFailure
16
  from functools import wraps
17
 
18
+ MONGODB_URI = os.environ.get("MONGODB_URI")
19
  DB_NAME = "otp_bot"
20
  COLLECTION_NAME = "accounts"
21
 
22
+ if MONGODB_URI:
23
  try:
24
+ mongo_client = MongoClient(MONGODB_URI)
25
  db = mongo_client[DB_NAME]
26
  accounts_collection = db[COLLECTION_NAME]
27
  mongo_client.admin.command('ping')
 
30
  print(f"❌ MongoDB Connection Failed: {e}")
31
  mongo_client = None
32
  else:
33
+ print("⚠️ MONGODB_URI tidak di set, menggunakan penyimpanan lokal")
34
  mongo_client = None
35
 
36
  print = lambda *args, **kwargs: __builtins__.print(*args, **kwargs, flush=True)
 
152
  app.secret_key = "fourstore-multi-account-secret"
153
  sse_clients = []
154
 
 
 
 
 
 
 
 
 
 
155
  def get_wib_time():
156
  return datetime.now(timezone.utc) + timedelta(hours=7)
157
 
 
444
  for q in dead:
445
  sse_clients.remove(q)
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()
 
475
 
476
  all_services = list(set([log.get('service') for log in all_logs if log.get('service')]))
477
 
478
+ total_otp = sum(len(acc.get('sent_cache', [])) for acc in accounts.values())
479
  today_otp = len(all_logs)
480
 
481
  accounts_list = []
482
+ for acc_id, acc in accounts.items():
483
  accounts_list.append({
484
  "id": acc_id,
485
  "username": mask_email(acc.get("username", "Unknown")),
486
  "status": acc.get("status", False),
487
+ "owner_id": acc.get("owner_id", "-"),
488
  "bot_token": "✅" if acc.get("bot_token") else "❌",
489
  "chat_id": acc.get("chat_id", "-")[:5] + "..." if acc.get("chat_id") and len(acc.get("chat_id")) > 5 else acc.get("chat_id", "-")
490
  })
 
508
  .nav-item {{ color: #8b949e; text-decoration: none; padding: 8px 16px; border-radius: 8px; transition: all 0.2s; }}
509
  .nav-item:hover {{ background: #2d3540; color: #00f2fe; }}
510
  .nav-item.active {{ background: #00f2fe20; color: #00f2fe; border: 1px solid #00f2fe40; }}
 
511
 
512
  .header {{ background: linear-gradient(145deg, #1a1f2c, #0f131c); border-radius: 24px; padding: 28px; margin-bottom: 28px; border: 1px solid #2d3540; }}
513
  .header-top {{ display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 15px; }}
 
529
  .btn-danger {{ background: #ff4d4d; color: white; }}
530
  .btn-small {{ padding: 6px 12px; font-size: 12px; }}
531
 
532
+ .account-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 20px; margin-bottom: 30px; }}
533
  .account-card {{ background: #1a1f2c; border-radius: 16px; padding: 20px; border: 1px solid #2d3540; position: relative; }}
534
  .account-card.online {{ border-color: #00f2fe; box-shadow: 0 0 15px #00f2fe20; }}
535
  .account-status {{ position: absolute; top: 20px; right: 20px; width: 12px; height: 12px; border-radius: 50%; }}
 
546
  .clear-btn {{ padding: 8px 16px; background: #2d3a4a; border: none; border-radius: 100px; color: white; font-size: 13px; cursor: pointer; text-decoration: none; }}
547
 
548
  .otp-section {{ background: #0f131c; border-radius: 24px; padding: 28px; border: 1px solid #2d3540; overflow-x: auto; }}
549
+ table {{ width: 100%; border-collapse: collapse; min-width: 1200px; }}
550
  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; }}
551
  td {{ padding: 16px 12px; border-bottom: 1px solid #262c38; font-size: 14px; }}
552
  .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; }}
553
  .service-badge {{ background: #2d3a4a; padding: 6px 14px; border-radius: 100px; font-size: 12px; font-weight: 600; display: inline-block; }}
554
  .account-badge {{ background: #4a3a2d; padding: 4px 10px; border-radius: 20px; font-size: 11px; display: inline-block; }}
555
+ .owner-badge {{ background: #2d4a3a; padding: 4px 10px; border-radius: 20px; font-size: 11px; display: inline-block; }}
556
  .whatsapp {{ background: #25D36620; color: #25D366; border: 1px solid #25D36640; }}
557
  .telegram {{ background: #26A5E420; color: #26A5E4; border: 1px solid #26A5E440; }}
558
  .number {{ font-family: monospace; }}
 
569
  <div class="nav-brand">OTP MULTI ACCOUNT</div>
570
  <div class="nav-menu">
571
  <a href="/" class="nav-item active">🏠 Dashboard</a>
 
 
 
572
  </div>
573
  </div>
574
 
 
581
  <div class="status-badge">● ONLINE</div>
582
  </div>
583
  <div class="stats-grid">
584
+ <div class="stat-card"><div class="stat-label">Total Akun</div><div class="stat-value">{len(accounts)}</div></div>
585
+ <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>
586
  <div class="stat-card"><div class="stat-label">Total OTP</div><div class="stat-value">{total_otp}</div></div>
587
  <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>
588
  </div>
 
595
  <input type="password" name="password" placeholder="Password" class="form-input" required>
596
  <input type="text" name="bot_token" placeholder="Bot Token (opsional)" class="form-input">
597
  <input type="text" name="chat_id" placeholder="Chat ID (opsional)" class="form-input">
598
+ <input type="text" name="owner_id" placeholder="Owner ID (opsional)" class="form-input">
599
  <button type="submit" class="btn">Tambah & Login Otomatis</button>
600
  </form>
601
  </div>
602
 
603
+ <h3 style="margin-bottom: 20px;">📋 Daftar Semua Akun</h3>
 
 
 
604
  <div class="account-grid">
605
+ {generate_account_cards(accounts_list)}
606
  </div>
607
 
608
  <div class="search-section">
 
618
  {''.join([f'<option value="{s}" {"selected" if filter_service == s else ""}>📱 {s}</option>' for s in sorted(all_services)])}
619
  </select>
620
  </div>
 
 
 
 
 
 
621
  <a href="/" class="clear-btn">✕ Reset</a>
622
  <span class="result-count">📊 {len(all_logs)} hasil</span>
623
  </div>
 
629
  <tr>
630
  <th>WIB</th>
631
  <th>Akun</th>
632
+ <th>Owner</th>
633
  <th>Country</th>
634
  <th>Number</th>
635
  <th>Service</th>
 
680
  .then(() => location.reload());
681
  }}
682
 
683
+ function deleteAccount(accountId) {{
684
+ if (confirm('Hapus akun ini?')) {{
685
+ fetch('/delete_account/' + accountId, {{method: 'GET'}})
686
+ .then(() => location.reload());
687
+ }}
688
+ }}
689
+
690
  function copyOTP(otp) {{
691
  navigator.clipboard.writeText(otp).then(() => {{
692
  const toast = document.createElement('div');
 
712
  """
713
  return html
714
 
715
+ def generate_account_cards(accounts_list):
716
  if not accounts_list:
717
  return '<div style="grid-column:1/-1; text-align:center; padding:40px; color:#8b949e;">Belum ada akun. Tambah akun di atas!</div>'
718
 
 
723
  <div class="account-card {status_class}">
724
  <div class="account-status status-{status_class}"></div>
725
  <h4>{acc['username']}</h4>
726
+ <p style="margin-top:8px; color:#8b949e; font-size:12px;">
727
+ <span class="owner-badge">👤 Owner: {acc['owner_id']}</span>
728
+ </p>
729
+ <p style="margin-top:8px; color:#8b949e; font-size:12px;">
730
+ Bot: {acc['bot_token']} | Chat: {acc['chat_id']}
731
+ </p>
732
  <p style="color:#8b949e; font-size:12px;">Status: {'🟢 Online' if acc['status'] else '🔴 Offline'}</p>
733
+ <div class="account-actions">
734
+ <button onclick="loginAccount('{acc['id']}')" class="btn btn-small">Login</button>
735
+ <button onclick="logoutAccount('{acc['id']}')" class="btn btn-small btn-danger">Logout</button>
736
+ <button onclick="deleteAccount('{acc['id']}')" class="btn btn-small btn-danger">Hapus</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
737
  </div>
738
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739
  """
740
+ return html
741
 
742
  def generate_otp_rows(logs, search_query):
743
  if not logs:
744
+ return '<tr><td colspan="8" class="empty-state">🔍 Belum ada OTP</td></tr>'
745
 
746
  rows = ""
747
  for log in logs[:100]:
 
751
  sms = log.get('sms', '')
752
  service = log.get('service', 'UNKNOWN')
753
  account = log.get('account_username', 'Unknown')
754
+ account_id = log.get('account_id', '')
755
+ owner_id = accounts.get(account_id, {}).get('owner_id', '-') if account_id else '-'
756
 
757
  if search_query:
758
  country = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', country, flags=re.I)
 
764
  <tr class="new-row">
765
  <td style="color:#00f2fe;">{log.get('time', '')}</td>
766
  <td><span class="account-badge">{account}</span></td>
767
+ <td><span class="owner-badge">{owner_id}</span></td>
768
  <td>{country}</td>
769
  <td><span class="number">{number}</span></td>
770
  <td><span class="service-badge {service_class}">{service}</span></td>
 
781
  password = request.form['password']
782
  bot_token = request.form.get('bot_token', '')
783
  chat_id = request.form.get('chat_id', '')
784
+ owner_id = request.form.get('owner_id', '')
785
 
786
  if not owner_id:
787
+ owner_id = "default"
788
 
789
  masked = mask_email(username)
790
  print(f"\n➕ TAMBAH AKUN BARU: {masked} (ID: {account_id}) (Owner: {owner_id})")
 
835
  return redirect('/')
836
 
837
  @app.route('/delete_account/<account_id>')
 
838
  def delete_account_route(account_id):
 
 
839
  if account_id in accounts:
 
 
 
840
  username = accounts[account_id].get('username', 'Unknown')
841
  masked = mask_email(username)
842
  print(f"\n🗑️ HAPUS AKUN: {masked} (ID: {account_id})")
 
848
  save_accounts_to_file(accounts)
849
  print(f"✅ Akun dihapus")
850
 
851
+ return redirect('/')
852
 
853
  @app.route('/login_account/<account_id>', methods=['POST'])
 
854
  def login_account_route(account_id):
 
855
  print(f"\n🔔🔔🔔 LOGIN ACCOUNT DIPANGGIL: {account_id} 🔔🔔🔔")
856
 
857
  if account_id in accounts:
 
 
 
858
  acc = accounts[account_id]
859
  masked = mask_email(acc.get('username', 'Unknown'))
860
  print(f"📋 Data akun: {masked}")
 
866
  acc['password'],
867
  acc.get('bot_token', ''),
868
  acc.get('chat_id', ''),
869
+ acc.get('owner_id', 'default')
870
  )
871
 
872
  print(f"📝 Result: {success} - {msg}")
 
881
  else:
882
  print(f"❌ Account ID {account_id} tidak ditemukan!")
883
 
884
+ return redirect('/')
885
 
886
  @app.route('/logout_account/<account_id>', methods=['POST'])
 
887
  def logout_account_route(account_id):
 
 
888
  if account_id in accounts:
 
 
 
889
  username = accounts[account_id].get('username', 'Unknown')
890
  masked = mask_email(username)
891
  print(f"\n🔌 LOGOUT: {masked} (ID: {account_id})")
 
899
  save_accounts_to_file(accounts)
900
  print(f"✅ Logout berhasil")
901
 
902
+ return redirect('/')
903
 
904
  @app.route('/stream')
905
  def stream():
 
1019
  print(" 📁 Data tersimpan di file accounts.json")
1020
  print(" 📋 LOGGING: FULL DETAIL")
1021
  print(" 🔒 EMAIL SENSOR: AKTIF")
1022
+ print(" 🤖 AUTO LOGIN: AKTIF")
1023
+ print(" 👑 OWNER ID: Input manual di form")
1024
  print("="*60 + "\n")
1025
 
1026
  for acc_id, acc in accounts.items():
 
1033
  acc['password'],
1034
  acc.get('bot_token', ''),
1035
  acc.get('chat_id', ''),
1036
+ acc.get('owner_id', 'default')
1037
  )
1038
  if success:
1039
  thread = Thread(target=run_account_scraper, args=(acc_id,), daemon=True)
 
1048
  save_accounts_to_file(accounts)
1049
 
1050
  print("\n✅ BOT SIAP! Dashboard: https://fourstore-otp.hf.space")
1051
+ print("📝 Owner ID bisa diisi manual saat tambah akun")
1052
 
1053
  while True:
1054
  time.sleep(60)