Fourstore commited on
Commit
c4e3a55
Β·
1 Parent(s): 8ff9efc
Files changed (1) hide show
  1. app.py +65 -480
app.py CHANGED
@@ -7,7 +7,7 @@ from flask import Flask, Response, request
7
  from threading import Thread
8
  from collections import deque
9
  import json
10
- import socket
11
 
12
  BASE = "http://159.69.3.189"
13
  LOGIN_URL = f"{BASE}/login"
@@ -25,7 +25,7 @@ sent_cache = set()
25
  csrf_token = None
26
  LOGIN_SUCCESS = False
27
 
28
- CUSTOM_DOMAIN = "https://fourstore-tele.hf.space"
29
 
30
  sms_cache = {}
31
  sms_counter = {}
@@ -40,10 +40,6 @@ def get_search_date():
40
  wib = get_wib_time()
41
  return (wib - timedelta(days=1)).strftime("%Y-%m-%d") if wib.hour < 7 else wib.strftime("%Y-%m-%d")
42
 
43
- def log(msg):
44
- timestamp = datetime.now().strftime("%H:%M:%S")
45
- print(f"[{timestamp}] {msg}")
46
-
47
  def login():
48
  global csrf_token, LOGIN_SUCCESS
49
  try:
@@ -78,12 +74,15 @@ def login():
78
  def get_ranges_with_count():
79
  try:
80
  date = get_search_date()
 
81
  r = session.post(GET_RANGE_URL, data={
82
  "_token": csrf_token,
83
  "from": date,
84
  "to": date
85
  }, timeout=15)
86
 
 
 
87
  soup = BeautifulSoup(r.text, "html.parser")
88
  ranges_data = []
89
 
@@ -99,9 +98,11 @@ def get_ranges_with_count():
99
  "name": rng,
100
  "count": count
101
  })
 
102
 
103
  return ranges_data
104
  except Exception as e:
 
105
  return []
106
 
107
  def get_numbers_with_count(rng):
@@ -133,9 +134,11 @@ def get_numbers_with_count(rng):
133
  "number": num,
134
  "count": count
135
  })
 
136
 
137
  return numbers_data
138
  except Exception as e:
 
139
  return []
140
 
141
  def get_sms_fast(rng, number):
@@ -161,8 +164,10 @@ def get_sms_fast(rng, number):
161
 
162
  soup = BeautifulSoup(r.text, "html.parser")
163
  results = []
 
 
164
 
165
- for card in soup.select("div.card.card-body"):
166
  try:
167
  service = "UNKNOWN"
168
  service_div = card.select_one("div.col-sm-4")
@@ -176,12 +181,14 @@ def get_sms_fast(rng, number):
176
  otp = extract_otp(sms)
177
  if otp:
178
  results.append((service, sms, otp))
 
179
  except:
180
  continue
181
 
182
  sms_cache[cache_key] = (time.time(), results)
183
  return results
184
- except:
 
185
  return []
186
 
187
  def extract_otp(text):
@@ -253,7 +260,6 @@ app = Flask('')
253
  def home():
254
  wib = get_wib_time()
255
  search_date = get_search_date()
256
-
257
  search_query = request.args.get('q', '').lower()
258
  filter_service = request.args.get('service', '')
259
 
@@ -272,392 +278,75 @@ def home():
272
 
273
  html = f"""
274
  <!DOCTYPE html>
275
- <html lang="en">
276
  <head>
277
- <meta charset="UTF-8">
278
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
279
- <title>OTP DASHBOARD Β· SEARCH</title>
280
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
281
  <style>
282
- * {{
283
- margin: 0;
284
- padding: 0;
285
- box-sizing: border-box;
286
- }}
287
-
288
- body {{
289
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
290
- background: #0a0c10;
291
- color: #e4e6eb;
292
- padding: 24px;
293
- }}
294
-
295
- .container {{
296
- max-width: 1400px;
297
- margin: 0 auto;
298
- }}
299
-
300
- .header {{
301
- background: linear-gradient(145deg, #1a1f2c, #0f131c);
302
- border-radius: 24px;
303
- padding: 28px;
304
- margin-bottom: 28px;
305
- border: 1px solid #2d3540;
306
- box-shadow: 0 8px 20px rgba(0,0,0,0.4);
307
- }}
308
-
309
- .header-top {{
310
- display: flex;
311
- justify-content: space-between;
312
- align-items: center;
313
- margin-bottom: 24px;
314
- }}
315
-
316
- .title h1 {{
317
- font-size: 28px;
318
- font-weight: 700;
319
- background: linear-gradient(135deg, #00f2fe, #4facfe);
320
- -webkit-background-clip: text;
321
- -webkit-text-fill-color: transparent;
322
- margin-bottom: 6px;
323
- }}
324
-
325
- .title p {{
326
- color: #8b949e;
327
- font-size: 14px;
328
- }}
329
-
330
- .status-badge {{
331
- padding: 10px 24px;
332
- border-radius: 100px;
333
- font-weight: 600;
334
- font-size: 14px;
335
- background: {'#0a4d3c' if LOGIN_SUCCESS else '#4a2c2c'};
336
- color: {'#a0f0d0' if LOGIN_SUCCESS else '#ffb3b3'};
337
- border: 1px solid {'#1a6e5a' if LOGIN_SUCCESS else '#6e3a3a'};
338
- }}
339
-
340
- .stats-grid {{
341
- display: grid;
342
- grid-template-columns: repeat(4, 1fr);
343
- gap: 20px;
344
- margin-top: 20px;
345
- }}
346
-
347
- .stat-card {{
348
- background: #1a1f2c;
349
- padding: 20px;
350
- border-radius: 20px;
351
- border: 1px solid #2d3540;
352
- }}
353
-
354
- .stat-label {{
355
- color: #8b949e;
356
- font-size: 13px;
357
- text-transform: uppercase;
358
- letter-spacing: 0.5px;
359
- margin-bottom: 8px;
360
- }}
361
-
362
- .stat-value {{
363
- font-size: 32px;
364
- font-weight: 700;
365
- color: #00f2fe;
366
- }}
367
-
368
- .search-section {{
369
- background: #0f131c;
370
- border-radius: 20px;
371
- padding: 20px;
372
- margin-bottom: 24px;
373
- border: 1px solid #2d3540;
374
- display: flex;
375
- gap: 16px;
376
- align-items: center;
377
- flex-wrap: wrap;
378
- }}
379
-
380
- .search-box {{
381
- flex: 2;
382
- min-width: 280px;
383
- position: relative;
384
- }}
385
-
386
- .search-icon {{
387
- position: absolute;
388
- left: 16px;
389
- top: 14px;
390
- color: #8b949e;
391
- }}
392
-
393
- .search-input {{
394
- width: 100%;
395
- padding: 14px 20px 14px 48px;
396
- background: #1a1f2c;
397
- border: 1px solid #2d3540;
398
- border-radius: 100px;
399
- color: #e4e6eb;
400
- font-size: 15px;
401
- transition: all 0.2s;
402
- }}
403
-
404
- .search-input:focus {{
405
- outline: none;
406
- border-color: #00f2fe;
407
- background: #1e2433;
408
- box-shadow: 0 0 0 3px #00f2fe20;
409
- }}
410
-
411
- .filter-box {{
412
- flex: 1;
413
- min-width: 180px;
414
- }}
415
-
416
- .filter-select {{
417
- width: 100%;
418
- padding: 14px 20px;
419
- background: #1a1f2c;
420
- border: 1px solid #2d3540;
421
- border-radius: 100px;
422
- color: #e4e6eb;
423
- font-size: 15px;
424
- cursor: pointer;
425
- }}
426
-
427
- .filter-select:focus {{
428
- outline: none;
429
- border-color: #00f2fe;
430
- }}
431
-
432
- .clear-btn {{
433
- padding: 14px 28px;
434
- background: #2d3a4a;
435
- border: none;
436
- border-radius: 100px;
437
- color: white;
438
- font-size: 14px;
439
- font-weight: 600;
440
- cursor: pointer;
441
- transition: all 0.2s;
442
- text-decoration: none;
443
- display: inline-block;
444
- }}
445
-
446
- .clear-btn:hover {{
447
- background: #3d4a5a;
448
- }}
449
-
450
- .result-count {{
451
- color: #8b949e;
452
- font-size: 13px;
453
- margin-left: 8px;
454
- }}
455
-
456
- .otp-section {{
457
- background: #0f131c;
458
- border-radius: 24px;
459
- padding: 28px;
460
- border: 1px solid #2d3540;
461
- }}
462
-
463
- .section-title {{
464
- font-size: 18px;
465
- font-weight: 600;
466
- color: #e4e6eb;
467
- margin-bottom: 20px;
468
- display: flex;
469
- align-items: center;
470
- gap: 10px;
471
- }}
472
-
473
- .section-title span {{
474
- background: #00f2fe20;
475
- padding: 6px 12px;
476
- border-radius: 100px;
477
- font-size: 12px;
478
- color: #00f2fe;
479
- }}
480
-
481
- table {{
482
- width: 100%;
483
- border-collapse: collapse;
484
- }}
485
-
486
- th {{
487
- text-align: left;
488
- padding: 16px 12px;
489
- background: #1a1f2c;
490
- color: #00f2fe;
491
- font-weight: 600;
492
- font-size: 13px;
493
- text-transform: uppercase;
494
- letter-spacing: 0.5px;
495
- border-bottom: 2px solid #2d3540;
496
- }}
497
-
498
- td {{
499
- padding: 16px 12px;
500
- border-bottom: 1px solid #262c38;
501
- font-size: 14px;
502
- }}
503
-
504
- .otp-badge {{
505
- background: #002b36;
506
- color: #00f2fe;
507
- font-family: 'Monaco', 'Menlo', monospace;
508
- font-size: 16px;
509
- font-weight: 700;
510
- padding: 6px 14px;
511
- border-radius: 100px;
512
- border: 1px solid #00f2fe40;
513
- display: inline-block;
514
- user-select: all;
515
- -webkit-user-select: all;
516
- -moz-user-select: all;
517
- cursor: pointer;
518
- transition: all 0.2s;
519
- }}
520
-
521
- .otp-badge:hover {{
522
- background: #003d4a;
523
- border-color: #00f2fe;
524
- transform: scale(1.05);
525
- }}
526
-
527
- .service-badge {{
528
- background: #2d3a4a;
529
- padding: 6px 14px;
530
- border-radius: 100px;
531
- font-size: 12px;
532
- font-weight: 600;
533
- color: #e4e6eb;
534
- display: inline-block;
535
- }}
536
-
537
- .service-badge.whatsapp {{ background: #25D36620; color: #25D366; border: 1px solid #25D36640; }}
538
- .service-badge.telegram {{ background: #26A5E420; color: #26A5E4; border: 1px solid #26A5E440; }}
539
- .service-badge.google {{ background: #EA433520; color: #EA4335; border: 1px solid #EA433540; }}
540
- .service-badge.facebook {{ background: #4267B220; color: #4267B2; border: 1px solid #4267B240; }}
541
- .service-badge.instagram {{ background: #E4405F20; color: #E4405F; border: 1px solid #E4405F40; }}
542
- .service-badge.tiktok {{ background: #00000020; color: #ffffff; border: 1px solid #ffffff40; }}
543
-
544
- .number {{
545
- font-family: 'Monaco', 'Menlo', monospace;
546
- color: #e4e6eb;
547
- }}
548
-
549
- .empty-state {{
550
- text-align: center;
551
- padding: 60px;
552
- color: #8b949e;
553
- }}
554
-
555
- .empty-state h3 {{
556
- font-size: 20px;
557
- margin-bottom: 12px;
558
- color: #b1bac4;
559
- }}
560
-
561
- .highlight {{
562
- background: #00f2fe30;
563
- border-radius: 4px;
564
- padding: 0 2px;
565
- }}
566
-
567
- @keyframes fadeIn {{
568
- from {{ opacity: 0; transform: translateY(-10px); }}
569
- to {{ opacity: 1; transform: translateY(0); }}
570
- }}
571
-
572
- .new-row {{
573
- animation: fadeIn 0.3s ease;
574
- background: linear-gradient(90deg, #00f2fe10, transparent);
575
- }}
576
-
577
- .toast {{
578
- position: fixed;
579
- bottom: 24px;
580
- right: 24px;
581
- background: #00f2fe;
582
- color: #000;
583
- padding: 14px 28px;
584
- border-radius: 100px;
585
- font-weight: 600;
586
- animation: slideIn 0.3s ease;
587
- z-index: 9999;
588
- box-shadow: 0 8px 20px rgba(0,242,254,0.3);
589
- }}
590
-
591
- @keyframes slideIn {{
592
- from {{ opacity: 0; transform: translateX(30px); }}
593
- to {{ opacity: 1; transform: translateX(0); }}
594
- }}
595
  </style>
596
  </head>
597
  <body>
598
  <div class="container">
599
  <div class="header">
600
- <div class="header-top">
601
  <div class="title">
602
- <h1>OTP MONITOR Β· SEARCH</h1>
603
  <p>{CUSTOM_DOMAIN}</p>
604
  </div>
605
  <div class="status-badge">
606
  {'● ONLINE' if LOGIN_SUCCESS else 'β—‹ OFFLINE'}
607
  </div>
608
  </div>
609
-
610
  <div class="stats-grid">
611
  <div class="stat-card">
612
- <div class="stat-label">Total OTP</div>
613
- <div class="stat-value" id="total-otp">{len(sent_cache)}</div>
614
  </div>
615
  <div class="stat-card">
616
- <div class="stat-label">Today</div>
617
- <div class="stat-value" id="today-otp">{len(otp_logs)}</div>
618
  </div>
619
  <div class="stat-card">
620
- <div class="stat-label">WIB</div>
621
- <div class="stat-value" id="wib-time">{wib.strftime('%H:%M:%S')}</div>
622
  </div>
623
  <div class="stat-card">
624
- <div class="stat-label">Date</div>
625
- <div class="stat-value">{search_date}</div>
626
  </div>
627
  </div>
628
  </div>
629
 
630
  <div class="search-section">
631
- <div class="search-box">
632
- <span class="search-icon">πŸ”</span>
633
- <form action="/" method="get" style="margin: 0;" id="searchForm">
634
- <input type="text"
635
- class="search-input"
636
- name="q"
637
- placeholder="Cari negara, nomor, OTP, atau SMS..."
638
- value="{request.args.get('q', '')}"
639
- autocomplete="off">
640
- </form>
641
- </div>
642
-
643
- <div class="filter-box">
644
- <select class="filter-select" name="service" onchange="window.location.href='/?' + (document.getElementById('searchForm').q.value ? 'q=' + document.getElementById('searchForm').q.value + '&' : '') + 'service=' + this.value">
645
- <option value="">πŸ“‹ Semua Service</option>
646
- {''.join([f'<option value="{s}" {"selected" if filter_service == s else ""}>πŸ“± {s}</option>' for s in sorted(all_services)])}
647
- </select>
648
- </div>
649
-
650
- <a href="/" class="clear-btn">βœ• Clear</a>
651
-
652
- <span class="result-count">πŸ“Š {len(filtered_logs)} results</span>
653
  </div>
654
 
655
  <div class="otp-section">
656
- <div class="section-title">
657
- πŸ“¨ OTP TERBARU
658
- <span>LIVE</span>
659
- </div>
660
-
661
  <div style="overflow-x: auto;">
662
  <table>
663
  <thead>
@@ -679,92 +368,11 @@ def home():
679
  </div>
680
 
681
  <script>
682
- let eventSource;
683
-
684
- function connectSSE() {{
685
- eventSource = new EventSource('/stream');
686
-
687
- eventSource.onmessage = function(e) {{
688
- try {{
689
- const data = JSON.parse(e.data);
690
- if (data.otp) {{
691
- addRow(data);
692
- updateStats();
693
- }}
694
- }} catch (err) {{
695
- console.error('SSE Error:', err);
696
- }}
697
- }};
698
-
699
- eventSource.onerror = function() {{
700
- setTimeout(connectSSE, 3000);
701
- }};
702
- }}
703
-
704
- function addRow(data) {{
705
- const tbody = document.getElementById('otp-table-body');
706
-
707
- if (tbody.children.length === 1 && tbody.children[0].innerHTML.includes('Belum ada OTP')) {{
708
- tbody.innerHTML = '';
709
- }}
710
-
711
- const row = tbody.insertRow(0);
712
- row.className = 'new-row';
713
-
714
- const serviceClass = data.service.toLowerCase().replace(' ', '');
715
-
716
- row.innerHTML = `
717
- <td style="color: #00f2fe;">${{data.time}}</td>
718
- <td>${{data.country}}</td>
719
- <td><span class="number">${{data.number}}</span></td>
720
- <td><span class="service-badge ${{serviceClass}}">${{data.service}}</span></td>
721
- <td><span class="otp-badge" onclick="copyOTP('${{data.otp}}')">${{data.otp}}</span></td>
722
- <td><div style="max-width: 350px; overflow: hidden; text-overflow: ellipsis;" title="${{data.sms}}">${{data.sms}}</div></td>
723
- `;
724
-
725
- while (tbody.children.length > 50) {{
726
- tbody.removeChild(tbody.lastChild);
727
- }}
728
- }}
729
-
730
- function copyOTP(otp) {{
731
- navigator.clipboard.writeText(otp).then(() => {{
732
- const toast = document.createElement('div');
733
- toast.className = 'toast';
734
- toast.textContent = 'βœ… OTP copied!';
735
- document.body.appendChild(toast);
736
- setTimeout(() => toast.remove(), 2000);
737
- }});
738
- }}
739
-
740
- function updateStats() {{
741
- const totalEl = document.getElementById('total-otp');
742
- const todayEl = document.getElementById('today-otp');
743
-
744
- if (totalEl) {{
745
- totalEl.textContent = parseInt(totalEl.textContent || 0) + 1;
746
- }}
747
- if (todayEl) {{
748
- todayEl.textContent = parseInt(todayEl.textContent || 0) + 1;
749
- }}
750
- }}
751
-
752
- function updateTime() {{
753
- const now = new Date();
754
- now.setHours(now.getHours() + 7);
755
- const timeStr = now.toISOString().substr(11, 8);
756
- const wibEl = document.getElementById('wib-time');
757
- if (wibEl) wibEl.textContent = timeStr;
758
- }}
759
-
760
- document.querySelector('.search-input')?.addEventListener('keypress', function(e) {{
761
- if (e.key === 'Enter') {{
762
- document.getElementById('searchForm').submit();
763
- }}
764
- }});
765
-
766
- connectSSE();
767
- setInterval(updateTime, 1000);
768
  </script>
769
  </body>
770
  </html>
@@ -773,38 +381,19 @@ def home():
773
 
774
  def generate_filtered_rows(filtered_logs, search_query=""):
775
  if not filtered_logs:
776
- return '''
777
- <tr>
778
- <td colspan="6" class="empty-state">
779
- <h3>πŸ” Tidak ada hasil</h3>
780
- <p>Coba kata kunci lain atau reset filter</p>
781
- </td>
782
- </tr>
783
- '''
784
 
785
  rows = ""
786
  for entry in filtered_logs[:50]:
787
- country = entry["country"]
788
- number = entry["number"]
789
- otp = entry["otp"]
790
- sms = entry["sms"]
791
-
792
- if search_query:
793
- country = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', country, flags=re.I)
794
- number = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', number, flags=re.I)
795
- otp = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', otp, flags=re.I)
796
- sms = re.sub(f'({re.escape(search_query)})', r'<span class="highlight">\1</span>', sms, flags=re.I)
797
-
798
  service_class = entry["service"].lower().replace(' ', '')
799
-
800
  rows += f'''
801
  <tr>
802
  <td style="color: #00f2fe;">{entry["time"]}</td>
803
- <td>{country}</td>
804
- <td><span class="number">{number}</span></td>
805
- <td><span class="service-badge {service_class}">{entry["service"]}</span></td>
806
- <td><span class="otp-badge" onclick="copyOTP('{entry["otp"]}')">{otp}</span></td>
807
- <td><div style="max-width: 350px; overflow: hidden; text-overflow: ellipsis;" title="{entry["sms"]}">{sms}</div></td>
808
  </tr>
809
  '''
810
  return rows
@@ -822,7 +411,6 @@ def stream():
822
  sse_clients.remove(q)
823
  return Response(generate(), mimetype="text/event-stream")
824
 
825
- from queue import Queue
826
  def run_server():
827
  app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)
828
 
@@ -832,7 +420,6 @@ def main():
832
  print("\n" + "="*60)
833
  print(" πŸ”₯ OTP BOT - RANGE FOCUS MODE")
834
  print(" ⚑ HANYA FOKUS KE RANGE YANG BERUBAH")
835
- print(" πŸ” DASHBOARD DENGAN SEARCH & FILTER")
836
  print("="*60 + "\n")
837
 
838
  for i in range(5):
@@ -892,9 +479,7 @@ def main():
892
  sms_id = f"{rng}-{num}-{otp}"
893
  if sms_id not in sent_cache:
894
  masked = mask_number(num)
895
-
896
  msg = f"πŸ”” *NEW OTP*\n🌍 {country}\nπŸ“ž `{masked}`\nπŸ’¬ {service}\nπŸ” `{otp}`\n\n{sms[:300]}"
897
-
898
  if tg_send(msg):
899
  sent_cache.add(sms_id)
900
  add_otp_log(country, masked, service, otp, sms)
 
7
  from threading import Thread
8
  from collections import deque
9
  import json
10
+ from queue import Queue
11
 
12
  BASE = "http://159.69.3.189"
13
  LOGIN_URL = f"{BASE}/login"
 
25
  csrf_token = None
26
  LOGIN_SUCCESS = False
27
 
28
+ CUSTOM_DOMAIN = "https://fourstore--fourstore79.replit.app"
29
 
30
  sms_cache = {}
31
  sms_counter = {}
 
40
  wib = get_wib_time()
41
  return (wib - timedelta(days=1)).strftime("%Y-%m-%d") if wib.hour < 7 else wib.strftime("%Y-%m-%d")
42
 
 
 
 
 
43
  def login():
44
  global csrf_token, LOGIN_SUCCESS
45
  try:
 
74
  def get_ranges_with_count():
75
  try:
76
  date = get_search_date()
77
+ print(f"πŸ” Fetch ranges date: {date}")
78
  r = session.post(GET_RANGE_URL, data={
79
  "_token": csrf_token,
80
  "from": date,
81
  "to": date
82
  }, timeout=15)
83
 
84
+ print(f"πŸ“Š Status: {r.status_code}")
85
+
86
  soup = BeautifulSoup(r.text, "html.parser")
87
  ranges_data = []
88
 
 
98
  "name": rng,
99
  "count": count
100
  })
101
+ print(f" πŸ“Œ {rng} - {count} SMS")
102
 
103
  return ranges_data
104
  except Exception as e:
105
+ print(f"❌ Error get_ranges: {e}")
106
  return []
107
 
108
  def get_numbers_with_count(rng):
 
134
  "number": num,
135
  "count": count
136
  })
137
+ print(f" πŸ“± {mask_number(num)} - {count} SMS")
138
 
139
  return numbers_data
140
  except Exception as e:
141
+ print(f" ❌ Error get_numbers: {e}")
142
  return []
143
 
144
  def get_sms_fast(rng, number):
 
164
 
165
  soup = BeautifulSoup(r.text, "html.parser")
166
  results = []
167
+ cards = soup.select("div.card.card-body")
168
+ print(f" πŸ“¨ {len(cards)} SMS cards")
169
 
170
+ for card in cards:
171
  try:
172
  service = "UNKNOWN"
173
  service_div = card.select_one("div.col-sm-4")
 
181
  otp = extract_otp(sms)
182
  if otp:
183
  results.append((service, sms, otp))
184
+ print(f" βœ… OTP: {otp} - {service}")
185
  except:
186
  continue
187
 
188
  sms_cache[cache_key] = (time.time(), results)
189
  return results
190
+ except Exception as e:
191
+ print(f" ❌ Error get_sms: {e}")
192
  return []
193
 
194
  def extract_otp(text):
 
260
  def home():
261
  wib = get_wib_time()
262
  search_date = get_search_date()
 
263
  search_query = request.args.get('q', '').lower()
264
  filter_service = request.args.get('service', '')
265
 
 
278
 
279
  html = f"""
280
  <!DOCTYPE html>
281
+ <html>
282
  <head>
283
+ <title>OTP DASHBOARD</title>
 
 
 
284
  <style>
285
+ * {{ margin: 0; padding: 0; box-sizing: border-box; }}
286
+ body {{ font-family: Arial; background: #0a0c10; color: #fff; padding: 20px; }}
287
+ .container {{ max-width: 1400px; margin: 0 auto; }}
288
+ .header {{ background: #1a1f2c; padding: 20px; border-radius: 10px; margin-bottom: 20px; }}
289
+ .title h1 {{ color: #00f2fe; }}
290
+ .status-badge {{ display: inline-block; padding: 5px 15px; border-radius: 20px;
291
+ background: {'#0a4d3c' if LOGIN_SUCCESS else '#4a2c2c'}; }}
292
+ .stats-grid {{ display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-top: 20px; }}
293
+ .stat-card {{ background: #1a1f2c; padding: 15px; border-radius: 10px; }}
294
+ .search-section {{ background: #1a1f2c; padding: 15px; border-radius: 10px; margin-bottom: 20px; }}
295
+ .search-input {{ width: 100%; padding: 10px; background: #0a0c10; border: 1px solid #2d3540;
296
+ color: #fff; border-radius: 5px; }}
297
+ .otp-section {{ background: #1a1f2c; padding: 20px; border-radius: 10px; }}
298
+ table {{ width: 100%; border-collapse: collapse; }}
299
+ th {{ text-align: left; padding: 10px; background: #0a0c10; color: #00f2fe; }}
300
+ td {{ padding: 10px; border-bottom: 1px solid #2d3540; }}
301
+ .otp-badge {{ background: #002b36; color: #00f2fe; padding: 5px 10px; border-radius: 5px;
302
+ font-weight: bold; cursor: pointer; }}
303
+ .service-badge {{ background: #2d3a4a; padding: 3px 8px; border-radius: 5px; font-size: 12px; }}
304
+ .number {{ font-family: monospace; }}
305
+ .empty-state {{ text-align: center; padding: 40px; color: #8b949e; }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  </style>
307
  </head>
308
  <body>
309
  <div class="container">
310
  <div class="header">
311
+ <div style="display: flex; justify-content: space-between;">
312
  <div class="title">
313
+ <h1>OTP MONITOR</h1>
314
  <p>{CUSTOM_DOMAIN}</p>
315
  </div>
316
  <div class="status-badge">
317
  {'● ONLINE' if LOGIN_SUCCESS else 'β—‹ OFFLINE'}
318
  </div>
319
  </div>
 
320
  <div class="stats-grid">
321
  <div class="stat-card">
322
+ <div>Total OTP</div>
323
+ <div style="font-size: 32px; color: #00f2fe;">{len(sent_cache)}</div>
324
  </div>
325
  <div class="stat-card">
326
+ <div>Today</div>
327
+ <div style="font-size: 32px; color: #00f2fe;">{len(otp_logs)}</div>
328
  </div>
329
  <div class="stat-card">
330
+ <div>WIB</div>
331
+ <div style="font-size: 32px; color: #00f2fe;">{wib.strftime('%H:%M:%S')}</div>
332
  </div>
333
  <div class="stat-card">
334
+ <div>Date</div>
335
+ <div style="font-size: 32px; color: #00f2fe;">{search_date}</div>
336
  </div>
337
  </div>
338
  </div>
339
 
340
  <div class="search-section">
341
+ <form action="/" method="get">
342
+ <input type="text" class="search-input" name="q"
343
+ placeholder="Cari negara, nomor, OTP, atau SMS..."
344
+ value="{request.args.get('q', '')}">
345
+ </form>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  </div>
347
 
348
  <div class="otp-section">
349
+ <h3 style="margin-bottom: 15px;">πŸ“¨ OTP TERBARU</h3>
 
 
 
 
350
  <div style="overflow-x: auto;">
351
  <table>
352
  <thead>
 
368
  </div>
369
 
370
  <script>
371
+ let eventSource = new EventSource('/stream');
372
+ eventSource.onmessage = function(e) {{
373
+ const data = JSON.parse(e.data);
374
+ if (data.otp) location.reload();
375
+ }};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  </script>
377
  </body>
378
  </html>
 
381
 
382
  def generate_filtered_rows(filtered_logs, search_query=""):
383
  if not filtered_logs:
384
+ return '<tr><td colspan="6" class="empty-state">πŸ” Tidak ada hasil</td></tr>'
 
 
 
 
 
 
 
385
 
386
  rows = ""
387
  for entry in filtered_logs[:50]:
 
 
 
 
 
 
 
 
 
 
 
388
  service_class = entry["service"].lower().replace(' ', '')
 
389
  rows += f'''
390
  <tr>
391
  <td style="color: #00f2fe;">{entry["time"]}</td>
392
+ <td>{entry["country"]}</td>
393
+ <td><span class="number">{entry["number"]}</span></td>
394
+ <td><span class="service-badge">{entry["service"]}</span></td>
395
+ <td><span class="otp-badge" onclick="navigator.clipboard.writeText('{entry["otp"]}')">{entry["otp"]}</span></td>
396
+ <td>{entry["sms"]}</td>
397
  </tr>
398
  '''
399
  return rows
 
411
  sse_clients.remove(q)
412
  return Response(generate(), mimetype="text/event-stream")
413
 
 
414
  def run_server():
415
  app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)
416
 
 
420
  print("\n" + "="*60)
421
  print(" πŸ”₯ OTP BOT - RANGE FOCUS MODE")
422
  print(" ⚑ HANYA FOKUS KE RANGE YANG BERUBAH")
 
423
  print("="*60 + "\n")
424
 
425
  for i in range(5):
 
479
  sms_id = f"{rng}-{num}-{otp}"
480
  if sms_id not in sent_cache:
481
  masked = mask_number(num)
 
482
  msg = f"πŸ”” *NEW OTP*\n🌍 {country}\nπŸ“ž `{masked}`\nπŸ’¬ {service}\nπŸ” `{otp}`\n\n{sms[:300]}"
 
483
  if tg_send(msg):
484
  sent_cache.add(sms_id)
485
  add_otp_log(country, masked, service, otp, sms)