Muttered3 commited on
Commit
54894b7
·
verified ·
1 Parent(s): dfd12ec

Update bot.py

Browse files
Files changed (1) hide show
  1. bot.py +25 -312
bot.py CHANGED
@@ -229,282 +229,16 @@ def setup_handlers(client):
229
  except Exception as e:
230
  await conv.send_message(f"❌ **Error:** `{str(e)}`")
231
  finally:
232
- try:
233
- await app.disconnect()
234
- except:
235
- pass
236
 
237
  async def _load_words(event, is_callback=True):
238
- all_lines = await asyncio.to_thread(read_file_sync)
239
- if not all_lines:
240
- return await event.edit("Empty file.")
241
-
242
- CHUNK = 200000
243
- current = all_lines[:CHUNK]
244
- remaining = all_lines[CHUNK:]
245
-
246
- valid = await asyncio.to_thread(filter_words_sync, current)
247
- r = await db.get_redis()
248
- done = await r.smembers("frag:done")
249
- to_q = list(valid - done)
250
-
251
- if to_q:
252
- pipe = r.pipeline()
253
- for i in range(0, len(to_q), 1000):
254
- pipe.lpush("frag:queue", *to_q[i:i+1000])
255
- await pipe.execute()
256
-
257
- await asyncio.to_thread(write_file_sync, remaining)
258
- await db.set_state(total=len(valid))
259
- await event.edit(f"✅ Loaded {len(to_q):,} words.", buttons=get_main_menu())
260
-
261
- @client.on(events.CallbackQuery())
262
- async def button_handler(event):
263
- if not is_admin(event): return
264
- try: await event.answer()
265
- except: pass
266
- data = event.data.decode('utf-8')
267
-
268
- if data == "back_main":
269
- await event.edit("**Fragment Control System**", buttons=get_main_menu())
270
- import os
271
- import re
272
- import time
273
- import io
274
- import asyncio
275
- from telethon import events, Button
276
- from PIL import Image, ImageDraw, ImageFont
277
- import db
278
- import scraper
279
-
280
- # --- AUTHENTICATION ---
281
- def get_admins():
282
- return {int(x) for x in os.environ.get("ADMIN_IDS", "").split(",") if x}
283
-
284
- def is_admin(event):
285
- return event.sender_id in get_admins()
286
-
287
- # --- PROFESSIONAL NAVIGATION ---
288
- def get_main_menu():
289
- return [
290
- [Button.inline("▶️ Start Scan", b"start_scan"), Button.inline("⏸ Pause", b"pause_scan"), Button.inline("⏹ Stop", b"stop_scan")],
291
- [Button.inline("📊 Live Status", b"show_status"), Button.inline("📥 Export Data", b"export_files")],
292
- [Button.inline("⚙️ System Settings", b"menu_settings")]
293
- ]
294
-
295
- def get_settings_menu():
296
- return [
297
- [Button.inline("💾 Load Database", b"load_words")],
298
- [Button.inline("⚡ Set Speed Limit", b"set_speed")],
299
- [Button.inline("🔴 Wipe Database", b"reset_confirm")],
300
- [Button.inline("« Back to Main Menu", b"back_main")]
301
- ]
302
-
303
- # --- FRAGMENT-STYLE IMAGE GENERATOR ---
304
- def generate_fragment_card(username, status):
305
- width, height = 1000, 450
306
- bg_color = (17, 24, 39)
307
- card_bg = (31, 41, 55)
308
- text_white = (255, 255, 255)
309
- text_gray = (156, 163, 175)
310
- text_cyan = (56, 189, 248)
311
- divider_color = (55, 65, 81)
312
-
313
- status_map = {
314
- "TAKEN": {"bg": (69, 39, 0), "text": (251, 191, 36), "desc": "Someone already claimed this username on Telegram. You can make an offer to the owner."},
315
- "AVAILABLE": {"bg": (6, 78, 59), "text": (52, 211, 153), "desc": "This username is currently available! You can secure it immediately on Fragment."},
316
- "FOR_SALE": {"bg": (30, 58, 138), "text": (147, 197, 253), "desc": "This username is listed for sale. You can purchase it using TON."},
317
- "ON_AUCTION": {"bg": (30, 58, 138), "text": (147, 197, 253), "desc": "This username is currently in an active auction. Place your bid now."},
318
- "SOLD": {"bg": (31, 41, 55), "text": (209, 213, 219), "desc": "This username was recently sold. It is currently locked to a new owner."},
319
- "UNAVAILABLE": {"bg": (64, 64, 64), "text": (212, 212, 212), "desc": "This username is reserved or prohibited by Telegram and cannot be claimed."}
320
- }
321
-
322
- cfg = status_map.get(status.upper(), status_map["UNAVAILABLE"])
323
- img = Image.new('RGB', (width, height), color=bg_color)
324
- draw = ImageDraw.Draw(img)
325
-
326
- try:
327
- font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
328
- font_main = ImageFont.truetype(font_path, 48)
329
- font_pill = ImageFont.truetype(font_path, 22)
330
- font_card_head = ImageFont.truetype(font_path, 26)
331
- font_card_body = ImageFont.truetype(font_path, 18)
332
- except:
333
- font_main = font_pill = font_card_head = font_card_body = ImageFont.load_default()
334
-
335
- # Header
336
- header_text = f"{username}.t.me"
337
- draw.text((40, 40), header_text, fill=text_white, font=font_main)
338
- tw = draw.textlength(header_text, font=font_main)
339
- pill_x = 40 + tw + 25
340
- draw.rounded_rectangle([pill_x, 52, pill_x + 135, 92], radius=8, fill=cfg["bg"])
341
- draw.text((pill_x + 22, 59), status.replace("_", " ").capitalize(), fill=cfg["text"], font=font_pill)
342
-
343
- # Left Card
344
- draw.rounded_rectangle([40, 150, 480, 400], radius=10, fill=card_bg)
345
- draw.text((105, 190), "Can I buy this username?", fill=text_white, font=font_card_head)
346
- draw.line([40, 235, 480, 235], fill=divider_color, width=1)
347
- draw.multiline_text((260, 315), cfg["desc"], fill=text_gray, font=font_card_body, align="center", anchor="mm", spacing=8)
348
-
349
- # Right Card
350
- draw.rounded_rectangle([510, 150, 960, 400], radius=10, fill=card_bg)
351
- rows = [("Telegram Username", f"@{username}"), ("Web Address", f"t.me/{username}"), ("TON Web 3.0", f"{username}.t.me")]
352
- y_pos = 185
353
- for i, (label, value) in enumerate(rows):
354
- draw.text((540, y_pos), label, fill=text_white, font=font_card_head)
355
- draw.text((930, y_pos), value, fill=text_cyan, font=font_card_head, anchor="ra")
356
- if i < 2:
357
- y_div = y_pos + 65
358
- draw.line([510, y_div, 960, y_div], fill=divider_color, width=1)
359
- y_pos += 85
360
-
361
- buf = io.BytesIO()
362
- img.save(buf, format='PNG')
363
- buf.seek(0)
364
- buf.name = f"{username}.png"
365
- return buf
366
-
367
- # --- SYSTEM DASHBOARD ---
368
- async def generate_status_msg():
369
- state = await db.get_state()
370
- counts = await db.get_counts()
371
- concurrency = await db.get_concurrency()
372
- qlen = await db.queue_size()
373
-
374
- total = int(state.get("total", 0))
375
- processed = int(state.get("processed", 0))
376
- pct = (processed / total * 100) if total > 0 else 0
377
-
378
- run_str = "RUNNING" if state.get("running") == "1" else "STOPPED"
379
- if state.get("paused") == "1": run_str = "PAUSED"
380
-
381
- bar_len = 20
382
- filled = int(bar_len * (processed / total)) if total > 0 else 0
383
- bar = "=" * filled + "-" * (bar_len - filled)
384
-
385
- return (
386
- f"**SYSTEM DASHBOARD**\n━━━━━━━━━━━━━━━━━━━━━\n"
387
- f"**State:** `{run_str}`\n**Queue:** `{qlen:,}` waiting\n"
388
- f"**Speed:** `{concurrency}` workers\n**Progress:** `{pct:.2f}%`\n"
389
- f"`[{bar}]` `{processed:,} / {total:,}`\n\n"
390
- f"**METRICS**\n━━━━━━━━━━━━━━━━━━━━━\n"
391
- f"Taken: `{counts.get('taken', 0):,}` | Unavail: `{counts.get('unavailable', 0):,}`\n"
392
- f"Sale: `{counts.get('forsale', 0):,}` | Auction: `{counts.get('auction', 0):,}`\n"
393
- f"Sold: `{counts.get('sold', 0):,}`"
394
- )
395
-
396
- # --- BACKGROUND HELPERS ---
397
- def read_file_sync():
398
- with open("words.txt", "r", encoding="utf-8") as f:
399
- return [line.strip().lower() for line in f if line.strip()]
400
-
401
- def write_file_sync(lines):
402
- with open("words.txt", "w", encoding="utf-8") as f:
403
- f.write("\n".join(lines))
404
-
405
- def filter_words_sync(chunk):
406
- return {w for w in chunk if re.match(r'^[a-z0-9_]{4,32}$', w)}
407
-
408
- # --- HANDLER SETUP ---
409
- def setup_handlers(client):
410
-
411
- @client.on(events.NewMessage(pattern='/start'))
412
- async def start_cmd(event):
413
- if is_admin(event):
414
- await event.respond("**Fragment Control System**", buttons=get_main_menu())
415
-
416
- @client.on(events.NewMessage(pattern=r'/check\s+([a-zA-Z0-9_]+)'))
417
- async def check_cmd(event):
418
- if not is_admin(event): return
419
- target = event.pattern_match.group(1).lower()
420
- msg = await event.respond(f"🔍 Analyzing `@{target}`...")
421
-
422
  try:
423
- status = await scraper.check_fragment(target)
424
- if not status: status = "UNAVAILABLE"
425
  except Exception:
426
- status = "ERROR"
427
 
428
- img = await asyncio.to_thread(generate_fragment_card, target, status)
429
- await msg.delete()
430
- await client.send_file(event.chat_id, file=img, caption=f"**Report:** `@{target}`")
431
-
432
- @client.on(events.NewMessage(pattern='/upload'))
433
- async def upload_cmd(event):
434
- if not is_admin(event): return
435
- if not event.is_reply:
436
- return await event.respond("Reply to a `.txt` file.")
437
- reply = await event.get_reply_message()
438
- if not reply.document:
439
- return await event.respond("Not a document.")
440
-
441
- wait = await event.respond("Downloading...")
442
- await reply.download_media(file="words.txt")
443
- await wait.edit("✅ Uploaded. Go to Settings -> Load Database.", buttons=get_main_menu())
444
-
445
- @client.on(events.NewMessage(pattern='/getstring'))
446
- async def pyrogram_string_cmd(event):
447
- if not is_admin(event): return
448
-
449
- async with client.conversation(event.chat_id, timeout=300) as conv:
450
- try:
451
- await conv.send_message("🛠 **Pyrogram String Session Generator**\n\nEnter your `API_ID`:")
452
- api_id_msg = await conv.get_response()
453
- api_id = int(api_id_msg.text.strip())
454
-
455
- await conv.send_message("Enter your `API_HASH`:")
456
- api_hash_msg = await conv.get_response()
457
- api_hash = api_hash_msg.text.strip()
458
-
459
- await conv.send_message("Enter the **Phone Number** (e.g., +1234567890):")
460
- phone_msg = await conv.get_response()
461
- phone = phone_msg.text.replace(" ", "").strip()
462
-
463
- await conv.send_message("⏳ Requesting OTP from Telegram...")
464
-
465
- from pyrogram import Client
466
- from pyrogram.errors import SessionPasswordNeeded
467
-
468
- app = Client("temp_session", api_id=api_id, api_hash=api_hash, in_memory=True)
469
- await app.connect()
470
-
471
- sent_code = await app.send_code(phone)
472
-
473
- await conv.send_message(
474
- "📥 **Code Sent!**\n\n"
475
- "⚠️ **CRITICAL:** You MUST enter the code with spaces between numbers.\n"
476
- "Example: `1 2 3 4 5`"
477
- )
478
- code_msg = await conv.get_response()
479
- otp_code = code_msg.text.replace(" ", "").strip()
480
-
481
- try:
482
- await app.sign_in(phone, sent_code.phone_code_hash, otp_code)
483
- except SessionPasswordNeeded:
484
- await conv.send_message("🔐 **2FA Password Required.**\nEnter your Cloud Password:")
485
- pwd_msg = await conv.get_response()
486
- await app.check_password(pwd_msg.text.strip())
487
-
488
- string_session = await app.export_session_string()
489
-
490
- await conv.send_message(
491
- f"✅ **String Session Generated!**\n\n"
492
- f"`{string_session}`\n\n"
493
- f"⚠️ Keep this secret. Anyone with this can access your account."
494
- )
495
-
496
- except asyncio.TimeoutError:
497
- await conv.send_message("❌ **Timeout.** You took too long. Run `/getstring` again.")
498
- except Exception as e:
499
- await conv.send_message(f"❌ **Error:** `{str(e)}`")
500
- finally:
501
- try:
502
- await app.disconnect()
503
- except:
504
- pass
505
-
506
- async def _load_words(event, is_callback=True):
507
- all_lines = await asyncio.to_thread(read_file_sync)
508
  if not all_lines:
509
  return await event.edit("Empty file.")
510
 
@@ -559,51 +293,30 @@ def setup_handlers(client):
559
  elif data.startswith("spd_"):
560
  await db.set_concurrency(int(data.split("_")[1]))
561
  await event.edit("✅ Speed Updated", buttons=get_settings_menu())
 
562
  elif data == "export_files":
563
- await event.edit("⏳ Exporting...")
564
- for s in ["taken", "unavailable", "sold", "forsale"]:
565
- lst = sorted(list(await (getattr(db, f"get_all_{s}")())))
566
- if lst:
567
- await client.send_file(event.chat_id, io.BytesIO("\n".join(lst).encode()), caption=f"{s}.txt")
568
- await event.edit("✅ Done", buttons=get_main_menu())
569
- elif data == "reset_confirm":
570
- await event.edit("⚠️ WIPE ALL?", buttons=[[Button.inline("CONFIRM", b"reset_do")], [Button.inline("Cancel", b"menu_settings")]])
571
- elif data == "reset_do":
572
- await db.flush_all()
573
- await event.edit("💣 Purged.", buttons=get_settings_menu())
574
- elif data == "menu_settings":
575
- await event.edit("**System Settings**", buttons=get_settings_menu())
576
- elif data == "load_words":
577
- await event.edit("⏳ Loading chunk...")
578
- await _load_words(event)
579
- elif data == "start_scan":
580
- await db.set_state(running="1", paused="0")
581
- await event.edit("▶️ Scanner Running", buttons=get_main_menu())
582
- elif data == "pause_scan":
583
- s = await db.get_state()
584
- p = "0" if s.get("paused") == "1" else "1"
585
- await db.set_state(paused=p)
586
- await event.edit("⏸ Scanner Paused" if p=="1" else "▶️ Resumed", buttons=get_main_menu())
587
- elif data == "stop_scan":
588
- await db.set_state(running="0", paused="0")
589
- await event.edit("⏹ Scanner Stopped", buttons=get_main_menu())
590
- elif data in ("show_status", "refresh_status"):
591
- await event.edit(await generate_status_msg(), buttons=[[Button.inline("🔄 Refresh", b"refresh_status")], [Button.inline("« Back", b"back_main")]])
592
- elif data == "set_speed":
593
- await event.edit("Select Speed:", buttons=[[Button.inline("10", b"spd_10"), Button.inline("20", b"spd_20")], [Button.inline("30", b"spd_30"), Button.inline("50", b"spd_50")], [Button.inline("« Back", b"menu_settings")]])
594
- elif data.startswith("spd_"):
595
- await db.set_concurrency(int(data.split("_")[1]))
596
- await event.edit("✅ Speed Updated", buttons=get_settings_menu())
597
- elif data == "export_files":
598
- await event.edit("⏳ Exporting...")
599
- for s in ["taken", "unavailable", "sold", "forsale"]:
600
  lst = sorted(list(await (getattr(db, f"get_all_{s}")())))
601
- if lst:
602
- await client.send_file(event.chat_id, io.BytesIO("\n".join(lst).encode()), caption=f"{s}.txt")
603
- await event.edit("✅ Done", buttons=get_main_menu())
 
 
 
 
 
 
 
 
 
 
604
  elif data == "reset_confirm":
605
  await event.edit("⚠️ WIPE ALL?", buttons=[[Button.inline("CONFIRM", b"reset_do")], [Button.inline("Cancel", b"menu_settings")]])
606
  elif data == "reset_do":
607
  await db.flush_all()
608
  await event.edit("💣 Purged.", buttons=get_settings_menu())
609
-
 
229
  except Exception as e:
230
  await conv.send_message(f"❌ **Error:** `{str(e)}`")
231
  finally:
232
+ if 'app' in locals():
233
+ try: await app.disconnect()
234
+ except: pass
 
235
 
236
  async def _load_words(event, is_callback=True):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  try:
238
+ all_lines = await asyncio.to_thread(read_file_sync)
 
239
  except Exception:
240
+ return await event.edit("❌ `words.txt` not found. Upload it first.")
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  if not all_lines:
243
  return await event.edit("Empty file.")
244
 
 
293
  elif data.startswith("spd_"):
294
  await db.set_concurrency(int(data.split("_")[1]))
295
  await event.edit("✅ Speed Updated", buttons=get_settings_menu())
296
+
297
  elif data == "export_files":
298
+ await event.edit("⏳ Exporting data... Generating text files.")
299
+ exported_count = 0
300
+
301
+ # BUG FIX: Added 'auction' to the list so we don't miss auction exports!
302
+ for s in ["taken", "unavailable", "sold", "forsale", "auction"]:
303
+ # Fetches the corresponding function dynamically from db.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  lst = sorted(list(await (getattr(db, f"get_all_{s}")())))
305
+ if lst:
306
+ # BUG FIX: Added the explicit .name attribute
307
+ f = io.BytesIO("\n".join(lst).encode('utf-8'))
308
+ f.name = f"{s}.txt"
309
+
310
+ await client.send_file(event.chat_id, file=f)
311
+ exported_count += 1
312
+
313
+ if exported_count > 0:
314
+ await event.edit(f"✅ Export complete! Sent {exported_count} files.", buttons=get_main_menu())
315
+ else:
316
+ await event.edit("⚠️ No data to export yet. Start a scan first!", buttons=get_main_menu())
317
+
318
  elif data == "reset_confirm":
319
  await event.edit("⚠️ WIPE ALL?", buttons=[[Button.inline("CONFIRM", b"reset_do")], [Button.inline("Cancel", b"menu_settings")]])
320
  elif data == "reset_do":
321
  await db.flush_all()
322
  await event.edit("💣 Purged.", buttons=get_settings_menu())