Fourstore commited on
Commit
050daaf
Β·
1 Parent(s): c4e3a55
Files changed (1) hide show
  1. app.py +186 -147
app.py CHANGED
@@ -25,7 +25,7 @@ sent_cache = set()
25
  csrf_token = None
26
  LOGIN_SUCCESS = False
27
 
28
- CUSTOM_DOMAIN = "https://fourstore--fourstore79.replit.app"
29
 
30
  sms_cache = {}
31
  sms_counter = {}
@@ -43,71 +43,124 @@ def get_search_date():
43
  def login():
44
  global csrf_token, LOGIN_SUCCESS
45
  try:
46
- print("πŸ” Login...")
 
 
 
 
47
  r = session.get(LOGIN_URL, timeout=30)
 
 
 
 
 
 
48
  soup = BeautifulSoup(r.text, "html.parser")
49
  token = soup.find("input", {"name": "_token"})
 
50
  if not token:
51
- print("❌ Token tidak ditemukan!")
 
 
52
  return False
 
53
  csrf_token = token.get("value")
 
54
 
55
- r = session.post(LOGIN_URL, data={
 
56
  "_token": csrf_token,
57
  "email": USERNAME,
58
  "password": PASSWORD
59
- }, timeout=30)
 
 
 
60
 
61
  if "dashboard" in r.text.lower() or "logout" in r.text.lower():
62
  LOGIN_SUCCESS = True
63
- print("βœ… Login BERHASIL!")
 
64
  return True
65
  else:
66
  LOGIN_SUCCESS = False
67
- print("❌ Login GAGAL!")
 
 
68
  return False
 
69
  except Exception as e:
70
  LOGIN_SUCCESS = False
71
- print(f"❌ Login Error: {e}")
72
  return False
73
 
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
 
89
- for item in soup.select(".item"):
90
- name_div = item.select_one(".col-sm-4")
91
- if not name_div: continue
92
- rng = name_div.get_text(strip=True)
93
-
94
- count_p = item.select_one(".col-3 .mb-0.pb-0")
95
- count = int(count_p.get_text(strip=True)) if count_p else 0
96
-
97
- ranges_data.append({
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):
109
  try:
 
 
 
110
  date = get_search_date()
 
 
111
  r = session.post(GET_NUMBER_URL, data={
112
  "_token": csrf_token,
113
  "start": date,
@@ -115,45 +168,69 @@ def get_numbers_with_count(rng):
115
  "range": rng
116
  }, timeout=15)
117
 
 
 
 
 
118
  soup = BeautifulSoup(r.text, "html.parser")
119
  numbers_data = []
120
 
121
- for div in soup.find_all("div", onclick=True):
122
- onclick = div.get("onclick", "")
123
- if "getDetialsNumber" in onclick:
124
- num = div.get_text(strip=True)
125
- if num and num.isdigit() and len(num) > 5:
126
- parent = div.find_parent("div", class_="card")
127
- count = 0
128
- if parent:
129
- p_tag = parent.find("p", class_="mb-0 pb-0")
130
- if p_tag:
131
- count = int(p_tag.get_text(strip=True))
132
 
133
- numbers_data.append({
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):
145
  try:
 
 
 
146
  date = get_search_date()
147
  cache_key = f"{rng}-{number}"
148
 
149
  if cache_key in sms_cache:
150
  timestamp, results = sms_cache[cache_key]
151
  if time.time() - timestamp < 5:
 
152
  return results
153
 
154
  parts = rng.split()
155
  range_name = parts[0] + " " + parts[1] if len(parts) >= 2 else rng
156
 
 
 
157
  r = session.post(GET_SMS_URL, data={
158
  "_token": csrf_token,
159
  "start": date,
@@ -162,10 +239,15 @@ def get_sms_fast(rng, number):
162
  "Range": range_name
163
  }, timeout=20)
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:
@@ -179,16 +261,22 @@ def get_sms_fast(rng, number):
179
  if msg_p:
180
  sms = msg_p.get_text(strip=True)
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):
@@ -222,11 +310,20 @@ def map_service(raw):
222
 
223
  def tg_send(msg):
224
  try:
225
- httpx.post(f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
226
- data={"chat_id": CHAT_ID, "text": msg, "parse_mode": "Markdown"},
227
- timeout=10)
228
- return True
229
- except:
 
 
 
 
 
 
 
 
 
230
  return False
231
 
232
  def add_otp_log(country, number, service, otp, sms):
@@ -274,125 +371,58 @@ def home():
274
  if filter_service:
275
  filtered_logs = [log for log in filtered_logs if log['service'] == filter_service]
276
 
277
- all_services = list(set([log['service'] for log in otp_logs]))
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>
353
- <tr>
354
- <th>WIB</th>
355
- <th>Country</th>
356
- <th>Number</th>
357
- <th>Service</th>
358
- <th>OTP</th>
359
- <th>Message</th>
360
- </tr>
361
- </thead>
362
- <tbody id="otp-table-body">
363
- {generate_filtered_rows(filtered_logs, search_query)}
364
- </tbody>
365
- </table>
366
- </div>
367
- </div>
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>
379
  """
380
  return 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
  '''
@@ -418,13 +448,17 @@ Thread(target=run_server, daemon=True).start()
418
 
419
  def main():
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):
 
426
  if login():
427
  break
 
 
428
  time.sleep(3)
429
 
430
  last_cleanup = time.time()
@@ -432,6 +466,7 @@ def main():
432
  while True:
433
  try:
434
  if not LOGIN_SUCCESS or not csrf_token:
 
435
  login()
436
  time.sleep(2)
437
  continue
@@ -440,11 +475,15 @@ def main():
440
  sms_cache.clear()
441
  sms_counter.clear()
442
  range_counter.clear()
443
- print("🧹 Cache cleared")
444
  last_cleanup = time.time()
445
 
446
  ranges_data = get_ranges_with_count()
447
- print(f"\nπŸ“‘ Scan {len(ranges_data)} ranges...")
 
 
 
 
448
 
449
  for range_item in ranges_data:
450
  rng = range_item["name"]
@@ -458,7 +497,6 @@ def main():
458
  range_counter[rng] = current_count
459
 
460
  numbers_data = get_numbers_with_count(rng)
461
- print(f" πŸ“ž Scan {len(numbers_data)} nomor...")
462
 
463
  for number_item in numbers_data:
464
  num = number_item["number"]
@@ -479,11 +517,12 @@ def main():
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)
486
- print(f" βœ… OTP: {otp} - {service}")
487
 
488
  print(f" βœ… Selesai proses {country}")
489
  time.sleep(0.5)
 
25
  csrf_token = None
26
  LOGIN_SUCCESS = False
27
 
28
+ CUSTOM_DOMAIN = "https://hf.co/spaces/rezaciledug/otp-monitor"
29
 
30
  sms_cache = {}
31
  sms_counter = {}
 
43
  def login():
44
  global csrf_token, LOGIN_SUCCESS
45
  try:
46
+ print("\n" + "="*50)
47
+ print("πŸ” PROSES LOGIN DIMULAI")
48
+ print("="*50)
49
+
50
+ print(f"πŸ“₯ Mengambil halaman login dari: {LOGIN_URL}")
51
  r = session.get(LOGIN_URL, timeout=30)
52
+ print(f"πŸ“Š Status code: {r.status_code}")
53
+
54
+ if r.status_code != 200:
55
+ print(f"❌ Gagal ambil halaman login! Status: {r.status_code}")
56
+ return False
57
+
58
  soup = BeautifulSoup(r.text, "html.parser")
59
  token = soup.find("input", {"name": "_token"})
60
+
61
  if not token:
62
+ print("❌ CSRF Token TIDAK DITEMUKAN!")
63
+ print("πŸ“„ Preview HTML 200 chars:")
64
+ print(r.text[:200])
65
  return False
66
+
67
  csrf_token = token.get("value")
68
+ print(f"βœ… CSRF Token berhasil diambil: {csrf_token[:20]}...")
69
 
70
+ print(f"πŸ“€ Mengirim data login untuk: {USERNAME}")
71
+ login_data = {
72
  "_token": csrf_token,
73
  "email": USERNAME,
74
  "password": PASSWORD
75
+ }
76
+
77
+ r = session.post(LOGIN_URL, data=login_data, timeout=30)
78
+ print(f"πŸ“Š Response login status: {r.status_code}")
79
 
80
  if "dashboard" in r.text.lower() or "logout" in r.text.lower():
81
  LOGIN_SUCCESS = True
82
+ print("βœ…βœ…βœ… LOGIN BERHASIL! βœ…βœ…βœ…")
83
+ print("🎯 Session aktif, siap scrape data")
84
  return True
85
  else:
86
  LOGIN_SUCCESS = False
87
+ print("❌❌❌ LOGIN GAGAL! ❌❌❌")
88
+ print("πŸ“„ Response preview:")
89
+ print(r.text[:300])
90
  return False
91
+
92
  except Exception as e:
93
  LOGIN_SUCCESS = False
94
+ print(f"❌ EXCEPTION SAAT LOGIN: {str(e)}")
95
  return False
96
 
97
  def get_ranges_with_count():
98
  try:
99
+ if not LOGIN_SUCCESS or not csrf_token:
100
+ print("⚠️ Session tidak valid, coba login ulang")
101
+ return []
102
+
103
  date = get_search_date()
104
+ print(f"\nπŸ” AMBIL DATA RANGE - Tanggal: {date}")
105
+
106
  r = session.post(GET_RANGE_URL, data={
107
  "_token": csrf_token,
108
  "from": date,
109
  "to": date
110
  }, timeout=15)
111
 
112
+ print(f"πŸ“Š Response status: {r.status_code}")
113
 
114
+ if r.status_code != 200:
115
+ print(f"❌ Gagal ambil range! Status: {r.status_code}")
116
+ return []
117
+
118
  soup = BeautifulSoup(r.text, "html.parser")
119
  ranges_data = []
120
 
121
+ items = soup.select(".item")
122
+ print(f"πŸ“¦ Ditemukan {len(items)} item range")
123
+
124
+ for idx, item in enumerate(items, 1):
125
+ try:
126
+ name_div = item.select_one(".col-sm-4")
127
+ if not name_div:
128
+ continue
129
+
130
+ rng = name_div.get_text(strip=True)
131
+ count_p = item.select_one(".col-3 .mb-0.pb-0")
132
+ count = 0
133
+
134
+ if count_p:
135
+ try:
136
+ count = int(count_p.get_text(strip=True))
137
+ except:
138
+ count = 0
139
+
140
+ ranges_data.append({
141
+ "name": rng,
142
+ "count": count
143
+ })
144
+ print(f" {idx}. {rng} - {count} SMS")
145
+
146
+ except Exception as e:
147
+ print(f" ❌ Error parse item {idx}: {e}")
148
+ continue
149
 
150
  return ranges_data
151
+
152
  except Exception as e:
153
+ print(f"❌ Error di get_ranges_with_count: {e}")
154
  return []
155
 
156
  def get_numbers_with_count(rng):
157
  try:
158
+ if not LOGIN_SUCCESS or not csrf_token:
159
+ return []
160
+
161
  date = get_search_date()
162
+ print(f"\n πŸ“ž AMBIL NOMOR untuk: {rng}")
163
+
164
  r = session.post(GET_NUMBER_URL, data={
165
  "_token": csrf_token,
166
  "start": date,
 
168
  "range": rng
169
  }, timeout=15)
170
 
171
+ if r.status_code != 200:
172
+ print(f" ❌ Gagal! Status: {r.status_code}")
173
+ return []
174
+
175
  soup = BeautifulSoup(r.text, "html.parser")
176
  numbers_data = []
177
 
178
+ divs = soup.find_all("div", onclick=True)
179
+ print(f" πŸ“¦ Ditemukan {len(divs)} div dengan onclick")
180
+
181
+ for div in divs:
182
+ try:
183
+ onclick = div.get("onclick", "")
184
+ if "getDetialsNumber" in onclick:
185
+ num = div.get_text(strip=True)
 
 
 
186
 
187
+ if num and num.isdigit() and len(num) > 5:
188
+ parent = div.find_parent("div", class_="card")
189
+ count = 0
190
+
191
+ if parent:
192
+ p_tag = parent.find("p", class_="mb-0 pb-0")
193
+ if p_tag:
194
+ try:
195
+ count = int(p_tag.get_text(strip=True))
196
+ except:
197
+ count = 0
198
+
199
+ numbers_data.append({
200
+ "number": num,
201
+ "count": count
202
+ })
203
+ print(f" βœ… {mask_number(num)} - {count} SMS")
204
+
205
+ except Exception as e:
206
+ print(f" ❌ Error: {e}")
207
+ continue
208
 
209
  return numbers_data
210
+
211
  except Exception as e:
212
+ print(f" ❌ Error di get_numbers_with_count: {e}")
213
  return []
214
 
215
  def get_sms_fast(rng, number):
216
  try:
217
+ if not LOGIN_SUCCESS or not csrf_token:
218
+ return []
219
+
220
  date = get_search_date()
221
  cache_key = f"{rng}-{number}"
222
 
223
  if cache_key in sms_cache:
224
  timestamp, results = sms_cache[cache_key]
225
  if time.time() - timestamp < 5:
226
+ print(f" πŸ’Ύ Cache hit: {len(results)} SMS")
227
  return results
228
 
229
  parts = rng.split()
230
  range_name = parts[0] + " " + parts[1] if len(parts) >= 2 else rng
231
 
232
+ print(f" πŸ“¨ AMBIL SMS untuk: {mask_number(number)}")
233
+
234
  r = session.post(GET_SMS_URL, data={
235
  "_token": csrf_token,
236
  "start": date,
 
239
  "Range": range_name
240
  }, timeout=20)
241
 
242
+ if r.status_code != 200:
243
+ print(f" ❌ Gagal! Status: {r.status_code}")
244
+ return []
245
+
246
  soup = BeautifulSoup(r.text, "html.parser")
 
247
  cards = soup.select("div.card.card-body")
248
+ results = []
249
+
250
+ print(f" πŸ“¦ Ditemukan {len(cards)} SMS cards")
251
 
252
  for card in cards:
253
  try:
 
261
  if msg_p:
262
  sms = msg_p.get_text(strip=True)
263
  otp = extract_otp(sms)
264
+
265
  if otp:
266
  results.append((service, sms, otp))
267
  print(f" βœ… OTP: {otp} - {service}")
268
+ else:
269
+ print(f" ⚠️ SMS tanpa OTP: {sms[:50]}...")
270
+
271
+ except Exception as e:
272
+ print(f" ❌ Error parse card: {e}")
273
  continue
274
 
275
  sms_cache[cache_key] = (time.time(), results)
276
  return results
277
+
278
  except Exception as e:
279
+ print(f" ❌ Error di get_sms_fast: {e}")
280
  return []
281
 
282
  def extract_otp(text):
 
310
 
311
  def tg_send(msg):
312
  try:
313
+ print(f" πŸ“€ Kirim ke Telegram: {msg[:50]}...")
314
+ response = httpx.post(f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage",
315
+ data={"chat_id": CHAT_ID, "text": msg, "parse_mode": "Markdown"},
316
+ timeout=10)
317
+
318
+ if response.status_code == 200:
319
+ print(f" βœ… Telegram BERHASIL")
320
+ return True
321
+ else:
322
+ print(f" ❌ Telegram GAGAL: {response.status_code}")
323
+ return False
324
+
325
+ except Exception as e:
326
+ print(f" ❌ Telegram Error: {e}")
327
  return False
328
 
329
  def add_otp_log(country, number, service, otp, sms):
 
371
  if filter_service:
372
  filtered_logs = [log for log in filtered_logs if log['service'] == filter_service]
373
 
 
 
374
  html = f"""
375
  <!DOCTYPE html>
376
  <html>
377
  <head>
378
+ <title>OTP MONITOR</title>
379
+ <meta http-equiv="refresh" content="5">
380
  <style>
 
381
  body {{ font-family: Arial; background: #0a0c10; color: #fff; padding: 20px; }}
382
  .container {{ max-width: 1400px; margin: 0 auto; }}
383
  .header {{ background: #1a1f2c; padding: 20px; border-radius: 10px; margin-bottom: 20px; }}
384
+ .status {{ color: {'#0f0' if LOGIN_SUCCESS else '#f00'}; }}
 
 
 
 
 
 
 
 
385
  table {{ width: 100%; border-collapse: collapse; }}
386
+ th {{ background: #2d3540; padding: 10px; }}
387
  td {{ padding: 10px; border-bottom: 1px solid #2d3540; }}
388
+ .otp {{ background: #002b36; color: #00f2fe; padding: 5px 10px; border-radius: 5px; }}
 
 
 
 
389
  </style>
390
  </head>
391
  <body>
392
  <div class="container">
393
  <div class="header">
394
+ <h1>OTP MONITOR</h1>
395
+ <p>Status: <span class="status">{'ONLINE' if LOGIN_SUCCESS else 'OFFLINE'}</span> | {wib.strftime('%H:%M:%S')} WIB</p>
396
+ <p>Total OTP: {len(sent_cache)} | Hari ini: {len(otp_logs)}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  </div>
398
 
399
+ <table>
400
+ <thead>
401
+ <tr><th>WIB</th><th>Country</th><th>Number</th><th>Service</th><th>OTP</th><th>Message</th></tr>
402
+ </thead>
403
+ <tbody>
404
+ {generate_filtered_rows(filtered_logs)}
405
+ </tbody>
406
+ </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  </div>
 
 
 
 
 
 
 
 
408
  </body>
409
  </html>
410
  """
411
  return html
412
 
413
+ def generate_filtered_rows(filtered_logs):
414
  if not filtered_logs:
415
+ return '<tr><td colspan="6" style="text-align:center">Belum ada OTP</td></tr>'
416
 
417
  rows = ""
418
  for entry in filtered_logs[:50]:
 
419
  rows += f'''
420
  <tr>
421
+ <td>{entry["time"]}</td>
422
  <td>{entry["country"]}</td>
423
+ <td>{entry["number"]}</td>
424
+ <td>{entry["service"]}</td>
425
+ <td><span class="otp">{entry["otp"]}</span></td>
426
  <td>{entry["sms"]}</td>
427
  </tr>
428
  '''
 
448
 
449
  def main():
450
  print("\n" + "="*60)
451
+ print(" πŸ”₯ OTP BOT - HUGGING FACE DEPLOY")
452
+ print(" πŸ“ PORT: 7860")
453
+ print(" 🌐 DOMAIN: hf.co/spaces/rezaciledug/otp-monitor")
454
  print("="*60 + "\n")
455
 
456
+ login_attempts = 0
457
+ while login_attempts < 5:
458
  if login():
459
  break
460
+ login_attempts += 1
461
+ print(f"⏳ Login gagal, percobaan ke-{login_attempts}/5...")
462
  time.sleep(3)
463
 
464
  last_cleanup = time.time()
 
466
  while True:
467
  try:
468
  if not LOGIN_SUCCESS or not csrf_token:
469
+ print("\n⚠️ Session expired, login ulang...")
470
  login()
471
  time.sleep(2)
472
  continue
 
475
  sms_cache.clear()
476
  sms_counter.clear()
477
  range_counter.clear()
478
+ print("\n🧹 Cache cleaned")
479
  last_cleanup = time.time()
480
 
481
  ranges_data = get_ranges_with_count()
482
+
483
+ if not ranges_data:
484
+ print("⚠️ Tidak ada data range")
485
+ time.sleep(5)
486
+ continue
487
 
488
  for range_item in ranges_data:
489
  rng = range_item["name"]
 
497
  range_counter[rng] = current_count
498
 
499
  numbers_data = get_numbers_with_count(rng)
 
500
 
501
  for number_item in numbers_data:
502
  num = number_item["number"]
 
517
  sms_id = f"{rng}-{num}-{otp}"
518
  if sms_id not in sent_cache:
519
  masked = mask_number(num)
520
+ msg = f"πŸ”” NEW OTP\n🌍 {country}\nπŸ“ž {masked}\nπŸ’¬ {service}\nπŸ” {otp}\n\n{sms[:300]}"
521
+
522
  if tg_send(msg):
523
  sent_cache.add(sms_id)
524
  add_otp_log(country, masked, service, otp, sms)
525
+ print(f" βœ… OTP: {otp}")
526
 
527
  print(f" βœ… Selesai proses {country}")
528
  time.sleep(0.5)