update
Browse files
app.py
CHANGED
|
@@ -25,7 +25,7 @@ sent_cache = set()
|
|
| 25 |
csrf_token = None
|
| 26 |
LOGIN_SUCCESS = False
|
| 27 |
|
| 28 |
-
CUSTOM_DOMAIN = "https://
|
| 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("
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
| 52 |
return False
|
|
|
|
| 53 |
csrf_token = token.get("value")
|
|
|
|
| 54 |
|
| 55 |
-
|
|
|
|
| 56 |
"_token": csrf_token,
|
| 57 |
"email": USERNAME,
|
| 58 |
"password": PASSWORD
|
| 59 |
-
}
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
if "dashboard" in r.text.lower() or "logout" in r.text.lower():
|
| 62 |
LOGIN_SUCCESS = True
|
| 63 |
-
print("β
|
|
|
|
| 64 |
return True
|
| 65 |
else:
|
| 66 |
LOGIN_SUCCESS = False
|
| 67 |
-
print("β
|
|
|
|
|
|
|
| 68 |
return False
|
|
|
|
| 69 |
except Exception as e:
|
| 70 |
LOGIN_SUCCESS = False
|
| 71 |
-
print(f"β
|
| 72 |
return False
|
| 73 |
|
| 74 |
def get_ranges_with_count():
|
| 75 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
date = get_search_date()
|
| 77 |
-
print(f"π
|
|
|
|
| 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"π
|
| 85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
soup = BeautifulSoup(r.text, "html.parser")
|
| 87 |
ranges_data = []
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
"
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
return ranges_data
|
|
|
|
| 104 |
except Exception as e:
|
| 105 |
-
print(f"β Error
|
| 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 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 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 |
-
|
| 134 |
-
"
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
return numbers_data
|
|
|
|
| 140 |
except Exception as e:
|
| 141 |
-
print(f"
|
| 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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
continue
|
| 187 |
|
| 188 |
sms_cache[cache_key] = (time.time(), results)
|
| 189 |
return results
|
|
|
|
| 190 |
except Exception as e:
|
| 191 |
-
print(f" β Error
|
| 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 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
| 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 |
-
.
|
| 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 {{
|
| 300 |
td {{ padding: 10px; border-bottom: 1px solid #2d3540; }}
|
| 301 |
-
.otp
|
| 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 |
-
<
|
| 312 |
-
|
| 313 |
-
|
| 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 |
-
<
|
| 341 |
-
<
|
| 342 |
-
<
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 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
|
| 383 |
if not filtered_logs:
|
| 384 |
-
return '<tr><td colspan="6"
|
| 385 |
|
| 386 |
rows = ""
|
| 387 |
for entry in filtered_logs[:50]:
|
| 388 |
-
service_class = entry["service"].lower().replace(' ', '')
|
| 389 |
rows += f'''
|
| 390 |
<tr>
|
| 391 |
-
<td
|
| 392 |
<td>{entry["country"]}</td>
|
| 393 |
-
<td>
|
| 394 |
-
<td>
|
| 395 |
-
<td><span class="otp
|
| 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 -
|
| 422 |
-
print("
|
|
|
|
| 423 |
print("="*60 + "\n")
|
| 424 |
|
| 425 |
-
|
|
|
|
| 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
|
| 444 |
last_cleanup = time.time()
|
| 445 |
|
| 446 |
ranges_data = get_ranges_with_count()
|
| 447 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"π
|
|
|
|
| 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}
|
| 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)
|