Muttered3 commited on
Commit
a34ee82
·
verified ·
1 Parent(s): bda1c1b

Update bot.py

Browse files
Files changed (1) hide show
  1. bot.py +107 -292
bot.py CHANGED
@@ -6,10 +6,14 @@ import asyncio
6
  from pyrogram import Client, filters
7
  from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
8
  from pyrogram.errors import MessageNotModified
9
- import db
 
 
10
  import scraper
11
  from curl_cffi.requests import AsyncSession
12
 
 
 
13
  # --- CONFIG & AUTH ---
14
  API_ID = int(os.environ.get("API_ID", 0))
15
  API_HASH = os.environ.get("API_HASH", "")
@@ -17,47 +21,47 @@ BOT_TOKEN = os.environ.get("BOT_TOKEN", "")
17
  ADMIN_IDS = [int(x) for x in os.environ.get("ADMIN_IDS", "").split(",") if x]
18
 
19
  app = Client("bot_session", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN)
20
-
21
- # Custom admin filter for Pyrogram
22
  is_admin = filters.user(ADMIN_IDS)
23
 
24
- # --- NAVIGATION MENUS ---
25
- def get_main_menu():
26
- return InlineKeyboardMarkup([
27
- [InlineKeyboardButton("▶️ Start", callback_data="start_scan"),
28
- InlineKeyboardButton("⏸ Pause", callback_data="pause_scan"),
29
- InlineKeyboardButton("⏹ Stop", callback_data="stop_scan")],
30
- [InlineKeyboardButton("📊 Live Status", callback_data="show_status"),
31
- InlineKeyboardButton("📥 Export Data", callback_data="export_files")],
32
- [InlineKeyboardButton("⚙️ System Settings", callback_data="menu_settings")]
33
- ])
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  def get_settings_menu():
36
  return InlineKeyboardMarkup([
37
- [InlineKeyboardButton("💾 Load Words", callback_data="load_words"),
38
- InlineKeyboardButton("📏 Set Length", callback_data="set_min_length")],
39
- [InlineKeyboardButton("⚡ Set Speed", callback_data="set_speed"),
40
- InlineKeyboardButton("🔴 Wipe DB", callback_data="reset_confirm")],
41
  [InlineKeyboardButton("« Back to Main", callback_data="back_main")]
42
  ])
43
 
44
- async def generate_status_msg():
45
- state = await db.get_state()
46
- counts = await db.get_counts()
47
- concurrency = await db.get_concurrency()
48
- qlen = await db.queue_size()
49
- proxy_count = await db.get_proxy_count()
50
-
51
- total = int(state.get("total", 0))
52
- processed = int(state.get("processed", 0))
53
- min_len = int(state.get("min_length", 4))
54
- pct = (processed / total * 100) if total > 0 else 0
55
 
56
- run_str = "🟢 RUNNING" if state.get("running") == "1" else " STOPPED"
57
- if state.get("paused") == "1": run_str = "⏸ PAUSED"
58
 
59
  bar_len = 20
60
- filled = int(bar_len * (processed / total)) if total > 0 else 0
61
  bar = "=" * filled + "-" * (bar_len - filled)
62
 
63
  return (
@@ -65,20 +69,19 @@ async def generate_status_msg():
65
  f"━━━━━━━━━━━━━━━━━━━━━\n"
66
  f"**State:** `{run_str}`\n"
67
  f"**Queue:** `{qlen:,}` waiting\n"
68
- f"**Speed:** `{concurrency}` workers\n"
69
- f"**Filter:** `Min {min_len} letters`\n"
70
- f"**Proxies:** `{proxy_count}` Active\n"
71
  f"**Progress:** `{pct:.2f}%`\n"
72
  f"`[{bar}]`\n"
73
- f"`{processed:,} / {total:,}`\n\n"
74
- f"**DATABASE METRICS**\n"
75
  f"━━━━━━━━━━━━━━━━━━━━━\n"
76
- f"���� Taken : `{counts.get('taken', 0):,}`\n"
77
- f"🚫 Unavail : `{counts.get('unavailable', 0):,}`\n"
78
- f"💰 For Sale : `{counts.get('forsale', 0):,}`\n"
79
- f"🔨 Auction : `{counts.get('auction', 0):,}`\n"
80
- f"✅ Available : `{counts.get('available', 0):,}`\n"
81
- f"🛒 Sold : `{counts.get('sold', 0):,}`\n"
82
  )
83
 
84
  # --- PROXY TESTER ---
@@ -89,16 +92,15 @@ async def test_proxy(proxy_url):
89
  async with AsyncSession(impersonate="chrome120", proxies=proxies) as session:
90
  resp = await session.get("https://fragment.com/", timeout=10)
91
  if resp.status_code == 200:
92
- latency = round((time.time() - start) * 1000)
93
- return proxy_url, latency
94
  except Exception:
95
  pass
96
  return proxy_url, None
97
 
98
- # --- MESSAGE HANDLERS ---
99
  @app.on_message(filters.command("start") & is_admin)
100
  async def start_cmd(client, message):
101
- await message.reply_text("**Fragment Control System**", reply_markup=get_main_menu())
102
 
103
  @app.on_message(filters.command("proxies") & is_admin)
104
  async def proxy_cmd(client, message):
@@ -119,294 +121,107 @@ async def proxy_cmd(client, message):
119
  tasks = [test_proxy(p) for p in raw_proxies]
120
  results = await asyncio.gather(*tasks)
121
 
122
- working_proxies = []
123
- latency_board = []
124
-
125
- for p_url, latency in results:
126
- if latency is not None:
127
- working_proxies.append(p_url)
128
- latency_board.append((p_url, latency))
129
-
130
- latency_board.sort(key=lambda x: x[1])
131
- await db.save_working_proxies(working_proxies)
132
-
133
- top_str = "\n".join([f"⏱ `{lat}ms` | {url.split('@')[-1] if '@' in url else url}" for url, lat in latency_board[:10]])
134
- report = (
135
- f"✅ **Proxy Test Complete**\n"
136
- f"━━━━━━━━━━━━━━━━\n"
137
- f"**Total Tested:** `{len(raw_proxies)}`\n"
138
- f"**Working & Saved:** `{len(working_proxies)}`\n\n"
139
- f"🏆 **Top Fastest Proxies:**\n{top_str if top_str else 'None'}"
140
- )
141
- await msg.edit_text(report, reply_markup=get_main_menu())
142
-
143
- @app.on_message(filters.command("check") & is_admin)
144
- async def check_cmd(client, message):
145
- if len(message.command) < 2: return await message.reply_text("Usage: `/check <word>`")
146
- target = message.command[1].lower()
147
- msg = await message.reply_text(f"📸 Taking live screenshot of `@{target}` on Fragment...")
148
-
149
- url = f"https://fragment.com/username/{target}"
150
- img_filename = None
151
 
152
- try:
153
- def take_screenshot():
154
- from html2image import Html2Image
155
- hti = Html2Image(
156
- browser_executable='/usr/bin/chromium',
157
- custom_flags=['--no-sandbox', '--disable-gpu', '--hide-scrollbars', '--disable-dev-shm-usage']
158
- )
159
- hti.size = (1000, 750)
160
- name = f"{target}_fragment.png"
161
- hti.screenshot(url=url, save_as=name)
162
- return name
163
-
164
- img_filename = await asyncio.to_thread(take_screenshot)
165
- await message.reply_document(document=img_filename, caption=f"**Live Web Snapshot:** `@{target}`")
166
- await msg.delete()
167
-
168
- except Exception as e:
169
- try: await msg.edit_text(f"❌ **Screenshot Error:**\n`{str(e)}`")
170
- except: pass
171
- finally:
172
- if img_filename and os.path.exists(img_filename):
173
- os.remove(img_filename)
174
-
175
- @app.on_message(filters.command("html") & is_admin)
176
- async def html_cmd(client, message):
177
- if len(message.command) < 2: return await message.reply_text("Usage: `/html <word>`")
178
- target = message.command[1].lower()
179
- msg = await message.reply_text(f"🔍 Fetching raw HTML for `@{target}` via Chrome Impersonation...")
180
-
181
- url = f"https://fragment.com/username/{target}"
182
-
183
- try:
184
- async with AsyncSession(impersonate="chrome120") as session:
185
- resp = await session.get(url, timeout=15, allow_redirects=True)
186
- html_text = resp.text
187
- status_code = resp.status_code
188
- final_url = str(resp.url)
189
-
190
- f = io.BytesIO(html_text.encode('utf-8'))
191
- f.name = f"{target}_fragment.html"
192
- f.seek(0)
193
-
194
- caption = (f"📄 **Raw Response for** `@{target}`\n"
195
- f"**Requested URL:** `{url}`\n"
196
- f"**Final URL:** `{final_url}`\n"
197
- f"**Status Code:** `{status_code}`")
198
-
199
- await message.reply_document(document=f, caption=caption)
200
- await msg.delete()
201
- except Exception as e:
202
- try: await msg.edit_text(f"❌ **Error fetching HTML:** `{str(e)}`")
203
- except: pass
204
-
205
- @app.on_message(filters.command("upload") & is_admin)
206
- async def upload_cmd(client, message):
207
- if not message.reply_to_message or not message.reply_to_message.document:
208
- return await message.reply_text("Reply to a `.txt` file.")
209
-
210
- msg = await message.reply_text("Downloading...")
211
- await message.reply_to_message.download(file_name="words.txt")
212
- await msg.edit_text("✅ Uploaded. Go to Settings -> Load Database.", reply_markup=get_main_menu())
213
-
214
-
215
- # --- STRING SESSION STATE MACHINE ---
216
- # Pyrogram doesn't have native "conversation" handlers, so we use Futures
217
- input_waiters = {}
218
-
219
- @app.on_message(filters.text & is_admin, group=-1)
220
- async def catch_input(client, message):
221
- chat_id = message.chat.id
222
- if chat_id in input_waiters and not input_waiters[chat_id].done():
223
- input_waiters[chat_id].set_result(message.text)
224
- message.stop_propagation()
225
-
226
- async def get_response(chat_id, timeout=300):
227
- loop = asyncio.get_running_loop()
228
- future = loop.create_future()
229
- input_waiters[chat_id] = future
230
- try: return await asyncio.wait_for(future, timeout)
231
- finally: del input_waiters[chat_id]
232
 
233
- @app.on_message(filters.command("getstring") & is_admin)
234
- async def pyrogram_string_cmd(client, message):
235
- chat_id = message.chat.id
236
- try:
237
- await message.reply_text("🛠 **Pyrogram Session Generator**\nEnter your `API_ID`:")
238
- api_id = int((await get_response(chat_id)).strip())
239
-
240
- await message.reply_text("Enter your `API_HASH`:")
241
- api_hash = (await get_response(chat_id)).strip()
242
-
243
- await message.reply_text("Enter the **Phone Number** (e.g., +1234567890):")
244
- phone = (await get_response(chat_id)).replace(" ", "").strip()
245
-
246
- await message.reply_text("⏳ Requesting OTP from Telegram...")
247
-
248
- from pyrogram import Client as TmpClient
249
- from pyrogram.errors import SessionPasswordNeeded
250
-
251
- tmp_app = TmpClient("temp_session", api_id=api_id, api_hash=api_hash, in_memory=True)
252
- await tmp_app.connect()
253
-
254
- sent_code = await tmp_app.send_code(phone)
255
-
256
- await message.reply_text("📥 **Code Sent!**\n⚠️ Enter code with spaces (e.g., `1 2 3 4 5`):")
257
- otp_code = (await get_response(chat_id)).replace(" ", "").strip()
258
-
259
- try:
260
- await tmp_app.sign_in(phone, sent_code.phone_code_hash, otp_code)
261
- except SessionPasswordNeeded:
262
- await message.reply_text("🔐 **2FA Password Required:**")
263
- pwd = (await get_response(chat_id)).strip()
264
- await tmp_app.check_password(pwd)
265
-
266
- string_session = await tmp_app.export_session_string()
267
- await message.reply_text(f"✅ **Session Generated!**\n\n`{string_session}`\n\n⚠️ Keep this secret.")
268
- except asyncio.TimeoutError:
269
- await message.reply_text("❌ **Timeout.** Run `/getstring` again.")
270
- except Exception as e:
271
- await message.reply_text(f"❌ **Error:** `{str(e)}`")
272
- finally:
273
- if 'tmp_app' in locals():
274
- try: await tmp_app.disconnect()
275
- except: pass
276
-
277
- # --- CALLBACK ROUTER ---
278
  @app.on_callback_query(is_admin)
279
  async def button_handler(client, query):
280
  data = query.data
281
-
282
  try:
283
  if data == "back_main":
284
- await query.edit_message_text("**Fragment Control System**", reply_markup=get_main_menu())
285
 
286
  elif data == "menu_settings":
287
  await query.edit_message_text("**System Settings**", reply_markup=get_settings_menu())
288
 
289
- elif data == "set_min_length":
290
- kb = InlineKeyboardMarkup([
291
- [InlineKeyboardButton("4 Letters", callback_data="min_len_4"), InlineKeyboardButton("5 Letters", callback_data="min_len_5")],
292
- [InlineKeyboardButton("6 Letters", callback_data="min_len_6"), InlineKeyboardButton("7 Letters", callback_data="min_len_7")],
293
- [InlineKeyboardButton("« Back", callback_data="menu_settings")]
294
- ])
295
- await query.edit_message_text("📏 **Select Minimum Word Length:**", reply_markup=kb)
296
-
297
- elif data.startswith("min_len_"):
298
- new_len = int(data.split("_")[2])
299
- await db.set_state(min_length=new_len)
300
- await query.edit_message_text(f" Filter updated to `{new_len}` letters.", reply_markup=get_settings_menu())
 
 
 
 
 
 
 
 
 
 
301
 
302
  elif data == "load_words":
303
- await query.edit_message_text("⏳ Processing and loading chunk...")
304
  try:
305
  with open("words.txt", "r", encoding="utf-8") as f:
306
  all_lines = [line.strip().lower() for line in f if line.strip()]
307
  except Exception:
308
  return await query.edit_message_text("❌ `words.txt` not found. Upload it first.")
309
 
310
- if not all_lines: return await query.edit_message_text("Empty file.")
311
-
312
- state = await db.get_state()
313
- min_length = int(state.get("min_length", 4))
314
-
315
  CHUNK = 200000
316
  current = all_lines[:CHUNK]
317
- remaining = all_lines[CHUNK:]
318
 
319
- pattern = re.compile(rf'^[a-z0-9_]{{{min_length},32}}$')
320
  valid = {w for w in current if pattern.match(w)}
321
 
322
- r = await db.get_redis()
323
- done = await r.smembers("frag:done")
324
- to_q = list(valid - done)
325
-
326
- if to_q:
327
- pipe = r.pipeline()
328
- for i in range(0, len(to_q), 1000): pipe.lpush("frag:queue", *to_q[i:i+1000])
329
- await pipe.execute()
330
-
331
- with open("words.txt", "w", encoding="utf-8") as f:
332
- f.write("\n".join(remaining))
333
-
334
- await db.set_state(total=len(valid))
335
- await query.edit_message_text(f"✅ Loaded {len(to_q):,} words.", reply_markup=get_main_menu())
336
-
337
- elif data == "start_scan":
338
- await db.set_state(running="1", paused="0")
339
- await query.edit_message_text("▶️ Scanner Running", reply_markup=get_main_menu())
340
-
341
- elif data == "pause_scan":
342
- s = await db.get_state()
343
- p = "0" if s.get("paused") == "1" else "1"
344
- await db.set_state(paused=p)
345
- await query.edit_message_text("⏸ Scanner Paused" if p=="1" else "▶️ Resumed", reply_markup=get_main_menu())
346
-
347
- elif data == "stop_scan":
348
- await db.set_state(running="0", paused="0")
349
- await query.edit_message_text("⏹ Scanner Stopped", reply_markup=get_main_menu())
350
 
351
  elif data in ("show_status", "refresh_status"):
352
- msg = await generate_status_msg()
353
  kb = InlineKeyboardMarkup([[InlineKeyboardButton("🔄 Refresh", callback_data="refresh_status")], [InlineKeyboardButton("« Back", callback_data="back_main")]])
354
  try: await query.edit_message_text(msg, reply_markup=kb)
355
  except MessageNotModified: pass
356
-
357
- elif data == "set_speed":
358
- kb = InlineKeyboardMarkup([
359
- [InlineKeyboardButton("10", callback_data="spd_10"), InlineKeyboardButton("20", callback_data="spd_20")],
360
- [InlineKeyboardButton("30", callback_data="spd_30"), InlineKeyboardButton("50", callback_data="spd_50")],
361
- [InlineKeyboardButton("« Back", callback_data="menu_settings")]
362
- ])
363
- await query.edit_message_text("⚡ **Select Concurrent Workers:**", reply_markup=kb)
364
-
365
- elif data.startswith("spd_"):
366
- await db.set_concurrency(int(data.split("_")[1]))
367
- await query.edit_message_text("✅ Speed Updated", reply_markup=get_settings_menu())
368
-
369
  elif data == "export_files":
370
- await query.edit_message_text("⏳ Generating text files...")
371
- exported_count = 0
372
-
373
  for s in ["taken", "unavailable", "sold", "forsale", "auction", "available"]:
374
- lst = sorted(list(await (getattr(db, f"get_all_{s}")())))
375
- if lst:
376
- f = io.BytesIO("\n".join(lst).encode('utf-8'))
377
- f.name = f"{s}.txt"
378
- await client.send_document(query.message.chat.id, document=f)
379
- exported_count += 1
380
-
381
- if os.path.exists("other.txt"):
382
- await client.send_document(query.message.chat.id, document="other.txt", caption="Unknown HTML Responses")
383
- exported_count += 1
384
 
385
- if exported_count > 0:
386
- await query.edit_message_text(f"✅ Export complete! Sent {exported_count} files.", reply_markup=get_main_menu())
387
  else:
388
- await query.edit_message_text("⚠️ No data to export yet.", reply_markup=get_main_menu())
389
 
390
- elif data == "reset_confirm":
391
- kb = InlineKeyboardMarkup([[InlineKeyboardButton("CONFIRM WIPE", callback_data="reset_do")], [InlineKeyboardButton("Cancel", callback_data="menu_settings")]])
392
- await query.edit_message_text("⚠️ WIPE ALL REDIS DATA?", reply_markup=kb)
393
-
394
- elif data == "reset_do":
395
- await db.flush_all()
396
- await query.edit_message_text("💣 Memory purged successfully.", reply_markup=get_settings_menu())
 
 
 
 
397
 
398
  except Exception as e:
399
  if not isinstance(e, MessageNotModified):
400
- print(f"Callback Error: {e}")
401
 
402
- # If run directly as a script
403
  if __name__ == "__main__":
404
- from worker import run_worker
405
 
406
  async def main_loop():
407
  await app.start()
408
- print("🤖 Pyrogram Bot Started!")
409
- asyncio.create_task(run_worker())
 
 
 
410
  from pyrogram import idle
411
  await idle()
412
  await app.stop()
 
6
  from pyrogram import Client, filters
7
  from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
8
  from pyrogram.errors import MessageNotModified
9
+
10
+ from state import state
11
+ from logger import get_logger
12
  import scraper
13
  from curl_cffi.requests import AsyncSession
14
 
15
+ log = get_logger()
16
+
17
  # --- CONFIG & AUTH ---
18
  API_ID = int(os.environ.get("API_ID", 0))
19
  API_HASH = os.environ.get("API_HASH", "")
 
21
  ADMIN_IDS = [int(x) for x in os.environ.get("ADMIN_IDS", "").split(",") if x]
22
 
23
  app = Client("bot_session", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN)
 
 
24
  is_admin = filters.user(ADMIN_IDS)
25
 
26
+ # --- DYNAMIC UI MENUS ---
27
+ def get_dynamic_menu():
28
+ """Generates toggle switches based on the current in-memory state."""
29
+ buttons = []
30
+
31
+ if state.status == "STOPPED":
32
+ buttons.append([InlineKeyboardButton("▶️ Start Scan", callback_data="toggle_start")])
33
+ elif state.status == "RUNNING":
34
+ buttons.append([
35
+ InlineKeyboardButton("⏸ Pause", callback_data="toggle_pause"),
36
+ InlineKeyboardButton("⏹ Stop", callback_data="toggle_stop")
37
+ ])
38
+ elif state.status == "PAUSED":
39
+ buttons.append([
40
+ InlineKeyboardButton("▶️ Resume", callback_data="toggle_resume"),
41
+ InlineKeyboardButton("⏹ Stop", callback_data="toggle_stop")
42
+ ])
43
+
44
+ buttons.append([InlineKeyboardButton("📊 Live Status", callback_data="show_status"), InlineKeyboardButton("📥 Export Data", callback_data="export_files")])
45
+ buttons.append([InlineKeyboardButton("⚙️ System Settings", callback_data="menu_settings")])
46
+
47
+ return InlineKeyboardMarkup(buttons)
48
 
49
  def get_settings_menu():
50
  return InlineKeyboardMarkup([
51
+ [InlineKeyboardButton("💾 Load Words", callback_data="load_words"), InlineKeyboardButton("📏 Set Length", callback_data="set_min_length")],
52
+ [InlineKeyboardButton(" Set Speed", callback_data="set_speed"), InlineKeyboardButton("🔴 Clear Memory", callback_data="reset_confirm")],
 
 
53
  [InlineKeyboardButton("« Back to Main", callback_data="back_main")]
54
  ])
55
 
56
+ def generate_status_msg():
57
+ qlen = state.queue.qsize() if state.queue else 0
58
+ pct = (state.processed / state.total_words * 100) if state.total_words > 0 else 0
 
 
 
 
 
 
 
 
59
 
60
+ run_str = f"🟢 {state.status}" if state.status == "RUNNING" else f"🔴 {state.status}"
61
+ if state.status == "PAUSED": run_str = "⏸ PAUSED"
62
 
63
  bar_len = 20
64
+ filled = int(bar_len * (state.processed / state.total_words)) if state.total_words > 0 else 0
65
  bar = "=" * filled + "-" * (bar_len - filled)
66
 
67
  return (
 
69
  f"━━━━━━━━━━━━━━━━━━━━━\n"
70
  f"**State:** `{run_str}`\n"
71
  f"**Queue:** `{qlen:,}` waiting\n"
72
+ f"**Speed:** `{state.concurrency}` workers\n"
73
+ f"**Proxies:** `{len(state.proxies)}` Active\n"
 
74
  f"**Progress:** `{pct:.2f}%`\n"
75
  f"`[{bar}]`\n"
76
+ f"`{state.processed:,} / {state.total_words:,}`\n\n"
77
+ f"**METRICS**\n"
78
  f"━━━━━━━━━━━━━━━━━━━━━\n"
79
+ f"🔴 Taken : `{state.counts.get('taken', 0):,}`\n"
80
+ f"🚫 Unavail : `{state.counts.get('unavailable', 0):,}`\n"
81
+ f"💰 For Sale : `{state.counts.get('forsale', 0):,}`\n"
82
+ f"🔨 Auction : `{state.counts.get('auction', 0):,}`\n"
83
+ f"✅ Available : `{state.counts.get('available', 0):,}`\n"
84
+ f"🛒 Sold : `{state.counts.get('sold', 0):,}`\n"
85
  )
86
 
87
  # --- PROXY TESTER ---
 
92
  async with AsyncSession(impersonate="chrome120", proxies=proxies) as session:
93
  resp = await session.get("https://fragment.com/", timeout=10)
94
  if resp.status_code == 200:
95
+ return proxy_url, round((time.time() - start) * 1000)
 
96
  except Exception:
97
  pass
98
  return proxy_url, None
99
 
100
+ # --- COMMANDS ---
101
  @app.on_message(filters.command("start") & is_admin)
102
  async def start_cmd(client, message):
103
+ await message.reply_text("**Fragment Control System**", reply_markup=get_dynamic_menu())
104
 
105
  @app.on_message(filters.command("proxies") & is_admin)
106
  async def proxy_cmd(client, message):
 
121
  tasks = [test_proxy(p) for p in raw_proxies]
122
  results = await asyncio.gather(*tasks)
123
 
124
+ working = [p for p, lat in results if lat is not None]
125
+ state.proxies = working # Save to memory
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ await msg.edit_text(f"✅ Saved **{len(working)}** working proxies to memory.", reply_markup=get_dynamic_menu())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
+ # --- UI CALLBACKS ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  @app.on_callback_query(is_admin)
131
  async def button_handler(client, query):
132
  data = query.data
 
133
  try:
134
  if data == "back_main":
135
+ await query.edit_message_text("**Fragment Control System**", reply_markup=get_dynamic_menu())
136
 
137
  elif data == "menu_settings":
138
  await query.edit_message_text("**System Settings**", reply_markup=get_settings_menu())
139
 
140
+ # DYNAMIC TOGGLES
141
+ elif data == "toggle_start":
142
+ if not state.queue or state.queue.empty():
143
+ return await query.answer("Queue is empty! Load words first.", show_alert=True)
144
+ state.status = "RUNNING"
145
+ log.info("Scanner Started")
146
+ await query.edit_message_text("▶️ Scanner Running", reply_markup=get_dynamic_menu())
147
+
148
+ elif data == "toggle_pause":
149
+ state.status = "PAUSED"
150
+ log.info("Scanner Paused")
151
+ await query.edit_message_text(" Scanner Paused", reply_markup=get_dynamic_menu())
152
+
153
+ elif data == "toggle_resume":
154
+ state.status = "RUNNING"
155
+ log.info("Scanner Resumed")
156
+ await query.edit_message_text("▶️ Scanner Resumed", reply_markup=get_dynamic_menu())
157
+
158
+ elif data == "toggle_stop":
159
+ state.status = "STOPPED"
160
+ log.info("Scanner Stopped")
161
+ await query.edit_message_text("⏹ Scanner Stopped", reply_markup=get_dynamic_menu())
162
 
163
  elif data == "load_words":
164
+ await query.edit_message_text("⏳ Processing and loading chunk into memory...")
165
  try:
166
  with open("words.txt", "r", encoding="utf-8") as f:
167
  all_lines = [line.strip().lower() for line in f if line.strip()]
168
  except Exception:
169
  return await query.edit_message_text("❌ `words.txt` not found. Upload it first.")
170
 
 
 
 
 
 
171
  CHUNK = 200000
172
  current = all_lines[:CHUNK]
 
173
 
174
+ pattern = re.compile(rf'^[a-z0-9_]{{{state.min_length},32}}$')
175
  valid = {w for w in current if pattern.match(w)}
176
 
177
+ await state.add_to_queue(list(valid))
178
+ await query.edit_message_text(f"✅ Loaded {len(valid):,} words into queue.", reply_markup=get_dynamic_menu())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
  elif data in ("show_status", "refresh_status"):
181
+ msg = generate_status_msg()
182
  kb = InlineKeyboardMarkup([[InlineKeyboardButton("🔄 Refresh", callback_data="refresh_status")], [InlineKeyboardButton("« Back", callback_data="back_main")]])
183
  try: await query.edit_message_text(msg, reply_markup=kb)
184
  except MessageNotModified: pass
185
+
 
 
 
 
 
 
 
 
 
 
 
 
186
  elif data == "export_files":
187
+ await query.edit_message_text("⏳ Preparing files from disk...")
188
+ exported = 0
 
189
  for s in ["taken", "unavailable", "sold", "forsale", "auction", "available"]:
190
+ if os.path.exists(f"results/{s}.txt"):
191
+ await client.send_document(query.message.chat.id, document=f"results/{s}.txt")
192
+ exported += 1
 
 
 
 
 
 
 
193
 
194
+ if exported > 0:
195
+ await query.edit_message_text(f"✅ Exported {exported} result files.", reply_markup=get_dynamic_menu())
196
  else:
197
+ await query.edit_message_text("⚠️ No data files found.", reply_markup=get_dynamic_menu())
198
 
199
+ elif data == "reset_confirm":
200
+ # Reset memory counters
201
+ state.processed = 0
202
+ state.total_words = 0
203
+ state.counts = {k: 0 for k in state.counts}
204
+ state.queue = asyncio.Queue()
205
+ state.status = "STOPPED"
206
+ # Delete output files
207
+ for file in os.listdir("results"):
208
+ os.remove(f"results/{file}")
209
+ await query.edit_message_text("💣 Memory and Results purged.", reply_markup=get_settings_menu())
210
 
211
  except Exception as e:
212
  if not isinstance(e, MessageNotModified):
213
+ log.exception("UI Callback Error:")
214
 
 
215
  if __name__ == "__main__":
216
+ from worker import manage_workers
217
 
218
  async def main_loop():
219
  await app.start()
220
+ log.info("🤖 Pyrogram Bot Started!")
221
+
222
+ # Start the worker manager daemon
223
+ asyncio.create_task(manage_workers())
224
+
225
  from pyrogram import idle
226
  await idle()
227
  await app.stop()