Muttered3 commited on
Commit
fbbf512
Β·
verified Β·
1 Parent(s): 363aa04

Update bot.py

Browse files
Files changed (1) hide show
  1. bot.py +52 -91
bot.py CHANGED
@@ -2,6 +2,7 @@ import os
2
  import re
3
  import time
4
  import io
 
5
  from telethon import events, Button
6
  import db
7
 
@@ -46,10 +47,10 @@ async def generate_status_msg():
46
  f"`[{bar}]`\n"
47
  f"━━━━━━━━━━━━━━━━━━━\n"
48
  f"πŸ”΄ Taken : `{counts['taken']:<10,}`\n"
49
- f"πŸ›’ Sold : `{counts['sold']:<10,}`\n"
50
  f"🚫 Unavail : `{counts['unavailable']:<10,}`\n"
51
  f"πŸ’° For Sale : `{counts['forsale']:<10,}`\n"
52
  f"πŸ”¨ Auction : `{counts['auction']:<10,}`\n"
 
53
  f"━━━━━━━━━━━━━━━━━━━\n"
54
  f"⚑ Speed : `{concurrency:<10}`\n"
55
  f"πŸ“‹ Queue : `{qlen:<10,}`\n"
@@ -60,23 +61,18 @@ def setup_handlers(client):
60
  @client.on(events.NewMessage(pattern='/start'))
61
  async def start_cmd(event):
62
  if not is_admin(event):
63
- await event.respond("β›” Unauthorized.")
64
  return
65
-
66
  await event.respond("πŸ‘‹ **Fragment Scanner Bot**\nSelect an action below:", buttons=get_main_buttons())
67
 
68
  @client.on(events.NewMessage(pattern='/upload'))
69
  async def upload_cmd(event):
70
  if not is_admin(event):
71
- await event.respond("β›” Unauthorized.")
72
  return
73
-
74
  if not event.is_reply:
75
  await event.respond("⚠️ Please reply to a `.txt` file with `/upload`.")
76
  return
77
 
78
  reply_msg = await event.get_reply_message()
79
-
80
  if not reply_msg.document or reply_msg.file.ext != '.txt':
81
  await event.respond("⚠️ The message you replied to must be a `.txt` document.")
82
  return
@@ -88,17 +84,13 @@ def setup_handlers(client):
88
  @client.on(events.NewMessage(pattern='/load'))
89
  async def load_cmd(event):
90
  if not is_admin(event):
91
- await event.respond("β›” Unauthorized.")
92
  return
93
  await _load_words(event, is_callback=False)
94
 
95
  async def _load_words(event, is_callback=True):
96
  async def update_msg(text):
97
  if is_callback:
98
- try:
99
- await event.edit(text, buttons=get_main_buttons())
100
- except Exception:
101
- pass
102
  else:
103
  await event.respond(text, buttons=get_main_buttons())
104
 
@@ -113,21 +105,26 @@ def setup_handlers(client):
113
  await update_msg("⚠️ The `words.txt` file on the server is empty. You've processed everything! Upload a new file.")
114
  return
115
 
 
116
  CHUNK_SIZE = 200000
117
  current_chunk = all_lines[:CHUNK_SIZE]
118
  remaining_lines = all_lines[CHUNK_SIZE:]
119
 
120
- valid_words = [w for w in set(current_chunk) if re.match(r'^[a-z0-9_]{4,32}$', w)]
 
121
 
122
  r = await db.get_redis()
123
  done = await r.smembers("frag:done")
124
 
125
- to_queue = [w for w in valid_words if w not in done]
 
126
 
127
  if to_queue:
128
  pipe = r.pipeline()
129
  for i in range(0, len(to_queue), 1000):
130
  pipe.lpush("frag:queue", *to_queue[i:i+1000])
 
 
131
 
132
  try:
133
  await pipe.execute()
@@ -162,6 +159,7 @@ def setup_handlers(client):
162
  pass
163
  return
164
 
 
165
  try:
166
  await event.answer()
167
  except Exception:
@@ -170,47 +168,30 @@ def setup_handlers(client):
170
  data = event.data.decode('utf-8')
171
 
172
  if data == "back_main":
173
- try:
174
- await event.edit("πŸ‘‹ **Fragment Scanner Bot**\nSelect an action below:", buttons=get_main_buttons())
175
- except Exception:
176
- pass
177
 
178
  elif data == "load_words":
179
- try:
180
- await event.edit("⏳ Reading and loading words... Please wait.")
181
- except Exception:
182
- pass
183
  await _load_words(event, is_callback=True)
184
 
185
  elif data == "start_scan":
186
  await db.set_state(running="1", paused="0", start_time=str(time.time()))
187
  qlen = await db.queue_size()
188
- try:
189
- await event.edit(f"πŸš€ **Scan started!**\n`{qlen:,}` words currently in the processing queue.", buttons=get_main_buttons())
190
- except Exception:
191
- pass
192
 
193
  elif data == "pause_scan":
194
  state = await db.get_state()
195
  if state.get("paused") == "1":
196
  await db.set_state(paused="0")
197
- try:
198
- await event.edit("▢️ **Scan resumed.**", buttons=get_main_buttons())
199
- except Exception:
200
- pass
201
  else:
202
  await db.set_state(paused="1")
203
- try:
204
- await event.edit("⏸ **Scan paused.**", buttons=get_main_buttons())
205
- except Exception:
206
- pass
207
 
208
  elif data == "stop_scan":
209
  await db.set_state(running="0", paused="0")
210
- try:
211
- await event.edit("⏹ **Scan stopped.** Progress has been saved.", buttons=get_main_buttons())
212
- except Exception:
213
- pass
214
 
215
  elif data in ("show_status", "refresh_status"):
216
  msg = await generate_status_msg()
@@ -221,7 +202,7 @@ def setup_handlers(client):
221
  try:
222
  await event.edit(msg, buttons=buttons)
223
  except Exception:
224
- pass
225
 
226
  elif data == "set_speed":
227
  buttons = [
@@ -229,10 +210,7 @@ def setup_handlers(client):
229
  [Button.inline("πŸš€ 50", b"spd_50"), Button.inline("⚑ 75", b"spd_75"), Button.inline("πŸ”₯ 100", b"spd_100")],
230
  [Button.inline("πŸ”™ Cancel", b"back_main")]
231
  ]
232
- try:
233
- await event.edit("⚑ **Select Scanner Speed (Concurrent Workers):**", buttons=buttons)
234
- except Exception:
235
- pass
236
 
237
  elif data.startswith("spd_"):
238
  try:
@@ -240,71 +218,54 @@ def setup_handlers(client):
240
  await db.set_concurrency(speed_limit)
241
  await event.edit(f"⚑ **Speed updated successfully!**\nNow running with `{speed_limit}` concurrent workers.", buttons=get_main_buttons())
242
  except Exception:
243
- try:
244
- await event.edit("❌ Error setting speed.", buttons=get_main_buttons())
245
- except Exception:
246
- pass
247
 
248
  elif data == "export_files":
249
- try:
250
- await event.edit("⏳ **Exporting data...**\nPlease wait while files are generated.", buttons=get_main_buttons())
251
- except Exception:
252
- pass
253
 
254
  taken = sorted(await db.get_all_taken())
255
  unavail = sorted(await db.get_all_unavailable())
256
  sold = sorted(await db.get_all_sold())
257
  forsale = sorted(await db.get_all_forsale())
258
- auction = sorted(await db.get_all_auction())
259
 
260
- files_to_send = [
261
- (taken, "taken.txt"),
262
- (unavail, "unavailable.txt"),
263
- (sold, "sold.txt"),
264
- (forsale, "forsale.txt"),
265
- (auction, "onauction.txt")
266
- ]
267
 
268
- sent_count = 0
269
- for data_list, filename in files_to_send:
270
- if data_list:
271
- f_io = io.BytesIO("\n".join(data_list).encode('utf-8'))
272
- f_io.name = filename
273
- await client.send_file(event.chat_id, file=f_io)
274
- sent_count += 1
275
 
276
- try:
277
- await event.edit(
278
- f"βœ… **Export Complete!**\n"
279
- f"Generated {sent_count} files.\n\n"
280
- f"πŸ“Š **Stats:**\n"
281
- f"Taken: {len(taken):,}\n"
282
- f"Sold: {len(sold):,}\n"
283
- f"For Sale: {len(forsale):,}\n"
284
- f"Auction: {len(auction):,}\n"
285
- f"Unavailable: {len(unavail):,}",
286
- buttons=get_main_buttons()
287
- )
288
- except Exception:
289
- pass
 
 
 
 
 
 
 
 
 
 
 
290
 
291
  elif data == "reset_confirm":
292
  buttons = [
293
  [Button.inline("βœ… Yes, WIPE ALL DATA", b"reset_do")],
294
  [Button.inline("❌ Cancel", b"back_main")]
295
  ]
296
- try:
297
- await event.edit("⚠️ **DANGER ZONE** ⚠️\n\nAre you sure you want to completely wipe the Redis database? All progress will be lost.", buttons=buttons)
298
- except Exception:
299
- pass
300
 
301
  elif data == "reset_do":
302
- try:
303
- await event.edit("⏳ **Wiping database...**")
304
- except Exception:
305
- pass
306
  await db.flush_all()
307
- try:
308
- await event.edit("πŸ’£ **Database wiped.**\n\nRedis is completely clear. Click **πŸ”„ Load Words** to grab the next batch from the server disk!", buttons=get_main_buttons())
309
- except Exception:
310
- pass
 
2
  import re
3
  import time
4
  import io
5
+ import asyncio
6
  from telethon import events, Button
7
  import db
8
 
 
47
  f"`[{bar}]`\n"
48
  f"━━━━━━━━━━━━━━━━━━━\n"
49
  f"πŸ”΄ Taken : `{counts['taken']:<10,}`\n"
 
50
  f"🚫 Unavail : `{counts['unavailable']:<10,}`\n"
51
  f"πŸ’° For Sale : `{counts['forsale']:<10,}`\n"
52
  f"πŸ”¨ Auction : `{counts['auction']:<10,}`\n"
53
+ f"πŸ›’ Sold : `{counts['sold']:<10,}`\n"
54
  f"━━━━━━━━━━━━━━━━━━━\n"
55
  f"⚑ Speed : `{concurrency:<10}`\n"
56
  f"πŸ“‹ Queue : `{qlen:<10,}`\n"
 
61
  @client.on(events.NewMessage(pattern='/start'))
62
  async def start_cmd(event):
63
  if not is_admin(event):
 
64
  return
 
65
  await event.respond("πŸ‘‹ **Fragment Scanner Bot**\nSelect an action below:", buttons=get_main_buttons())
66
 
67
  @client.on(events.NewMessage(pattern='/upload'))
68
  async def upload_cmd(event):
69
  if not is_admin(event):
 
70
  return
 
71
  if not event.is_reply:
72
  await event.respond("⚠️ Please reply to a `.txt` file with `/upload`.")
73
  return
74
 
75
  reply_msg = await event.get_reply_message()
 
76
  if not reply_msg.document or reply_msg.file.ext != '.txt':
77
  await event.respond("⚠️ The message you replied to must be a `.txt` document.")
78
  return
 
84
  @client.on(events.NewMessage(pattern='/load'))
85
  async def load_cmd(event):
86
  if not is_admin(event):
 
87
  return
88
  await _load_words(event, is_callback=False)
89
 
90
  async def _load_words(event, is_callback=True):
91
  async def update_msg(text):
92
  if is_callback:
93
+ await event.edit(text, buttons=get_main_buttons())
 
 
 
94
  else:
95
  await event.respond(text, buttons=get_main_buttons())
96
 
 
105
  await update_msg("⚠️ The `words.txt` file on the server is empty. You've processed everything! Upload a new file.")
106
  return
107
 
108
+ # Load chunks safely
109
  CHUNK_SIZE = 200000
110
  current_chunk = all_lines[:CHUNK_SIZE]
111
  remaining_lines = all_lines[CHUNK_SIZE:]
112
 
113
+ # OPTIMIZATION 1: Fast regex filtering
114
+ valid_words = {w for w in current_chunk if re.match(r'^[a-z0-9_]{4,32}$', w)}
115
 
116
  r = await db.get_redis()
117
  done = await r.smembers("frag:done")
118
 
119
+ # OPTIMIZATION 2: Mathematical Set Subtraction (Instant calculation instead of freezing CPU)
120
+ to_queue = list(valid_words - done)
121
 
122
  if to_queue:
123
  pipe = r.pipeline()
124
  for i in range(0, len(to_queue), 1000):
125
  pipe.lpush("frag:queue", *to_queue[i:i+1000])
126
+ # OPTIMIZATION 3: Yield to event loop so bot buttons stay smooth
127
+ await asyncio.sleep(0)
128
 
129
  try:
130
  await pipe.execute()
 
159
  pass
160
  return
161
 
162
+ # Instantly tell Telegram we received the click to stop the loading clock
163
  try:
164
  await event.answer()
165
  except Exception:
 
168
  data = event.data.decode('utf-8')
169
 
170
  if data == "back_main":
171
+ await event.edit("πŸ‘‹ **Fragment Scanner Bot**\nSelect an action below:", buttons=get_main_buttons())
 
 
 
172
 
173
  elif data == "load_words":
174
+ # Immediate UI feedback
175
+ await event.edit("⏳ **Reading and loading words... Please wait.**")
 
 
176
  await _load_words(event, is_callback=True)
177
 
178
  elif data == "start_scan":
179
  await db.set_state(running="1", paused="0", start_time=str(time.time()))
180
  qlen = await db.queue_size()
181
+ await event.edit(f"πŸš€ **Scan started!**\n`{qlen:,}` words currently in the processing queue.", buttons=get_main_buttons())
 
 
 
182
 
183
  elif data == "pause_scan":
184
  state = await db.get_state()
185
  if state.get("paused") == "1":
186
  await db.set_state(paused="0")
187
+ await event.edit("▢️ **Scan resumed.**", buttons=get_main_buttons())
 
 
 
188
  else:
189
  await db.set_state(paused="1")
190
+ await event.edit("⏸ **Scan paused.**", buttons=get_main_buttons())
 
 
 
191
 
192
  elif data == "stop_scan":
193
  await db.set_state(running="0", paused="0")
194
+ await event.edit("⏹ **Scan stopped.** Progress has been saved.", buttons=get_main_buttons())
 
 
 
195
 
196
  elif data in ("show_status", "refresh_status"):
197
  msg = await generate_status_msg()
 
202
  try:
203
  await event.edit(msg, buttons=buttons)
204
  except Exception:
205
+ pass
206
 
207
  elif data == "set_speed":
208
  buttons = [
 
210
  [Button.inline("πŸš€ 50", b"spd_50"), Button.inline("⚑ 75", b"spd_75"), Button.inline("πŸ”₯ 100", b"spd_100")],
211
  [Button.inline("πŸ”™ Cancel", b"back_main")]
212
  ]
213
+ await event.edit("⚑ **Select Scanner Speed (Concurrent Workers):**", buttons=buttons)
 
 
 
214
 
215
  elif data.startswith("spd_"):
216
  try:
 
218
  await db.set_concurrency(speed_limit)
219
  await event.edit(f"⚑ **Speed updated successfully!**\nNow running with `{speed_limit}` concurrent workers.", buttons=get_main_buttons())
220
  except Exception:
221
+ await event.edit("❌ Error setting speed.", buttons=get_main_buttons())
 
 
 
222
 
223
  elif data == "export_files":
224
+ await event.edit("⏳ **Exporting data...**\nPlease wait while files are generated.")
 
 
 
225
 
226
  taken = sorted(await db.get_all_taken())
227
  unavail = sorted(await db.get_all_unavailable())
228
  sold = sorted(await db.get_all_sold())
229
  forsale = sorted(await db.get_all_forsale())
 
230
 
231
+ await asyncio.sleep(0) # Keep UI smooth
 
 
 
 
 
 
232
 
233
+ exported_count = 0
 
 
 
 
 
 
234
 
235
+ if taken:
236
+ f_taken = io.BytesIO("\n".join(taken).encode('utf-8'))
237
+ f_taken.name = "taken.txt"
238
+ await client.send_file(event.chat_id, file=f_taken)
239
+ exported_count += 1
240
+
241
+ if unavail:
242
+ f_unavail = io.BytesIO("\n".join(unavail).encode('utf-8'))
243
+ f_unavail.name = "unavailable.txt"
244
+ await client.send_file(event.chat_id, file=f_unavail)
245
+ exported_count += 1
246
+
247
+ if sold:
248
+ f_sold = io.BytesIO("\n".join(sold).encode('utf-8'))
249
+ f_sold.name = "sold.txt"
250
+ await client.send_file(event.chat_id, file=f_sold)
251
+ exported_count += 1
252
+
253
+ if forsale:
254
+ f_forsale = io.BytesIO("\n".join(forsale).encode('utf-8'))
255
+ f_forsale.name = "forsale.txt"
256
+ await client.send_file(event.chat_id, file=f_forsale)
257
+ exported_count += 1
258
+
259
+ await event.edit(f"βœ… **Export Complete!**\nGenerated `{exported_count}` text files.", buttons=get_main_buttons())
260
 
261
  elif data == "reset_confirm":
262
  buttons = [
263
  [Button.inline("βœ… Yes, WIPE ALL DATA", b"reset_do")],
264
  [Button.inline("❌ Cancel", b"back_main")]
265
  ]
266
+ await event.edit("⚠️ **DANGER ZONE** ⚠️\n\nAre you sure you want to completely wipe the Redis database? All progress will be lost.", buttons=buttons)
 
 
 
267
 
268
  elif data == "reset_do":
269
+ await event.edit("⏳ **Wiping database...**")
 
 
 
270
  await db.flush_all()
271
+ await event.edit("πŸ’£ **Database wiped.**\n\nRedis is completely clear. Click **πŸ”„ Load Words** to grab the next batch from the server disk!", buttons=get_main_buttons())