Chandima Prabhath commited on
Commit
3e7203b
Β·
1 Parent(s): a30ed82

re edit images

Browse files
Files changed (1) hide show
  1. app.py +94 -89
app.py CHANGED
@@ -5,6 +5,7 @@ Description: A comprehensive WhatsApp bot with a professional, class-based struc
5
  Features include image generation, image editing, voice replies,
6
  and various utility functions, all handled by an asynchronous task queue.
7
  Includes request logging and chat ID filtering.
 
8
  """
9
 
10
  import os
@@ -299,9 +300,13 @@ class WhatsAppBot:
299
  self.conv_manager = ConversationManager(config.MAX_HISTORY_SIZE)
300
  self.intent_router = IntentRouter(self.conv_manager, self.logger)
301
  self.task_queue = queue.Queue()
302
- # Cache to store recent image message IDs and their download URLs
 
303
  self.image_cache = defaultdict(lambda: deque(maxlen=50))
304
- self.fastapi_app = FastAPI(title="WhatsApp Eve Bot", version="2.0.0")
 
 
 
305
  self._setup_routes()
306
  self._start_workers()
307
 
@@ -314,10 +319,8 @@ class WhatsAppBot:
314
 
315
  try:
316
  payload = await request.json()
317
- # Log the entire incoming request payload for debugging
318
  self.logger.debug(f"Incoming webhook payload: {json.dumps(payload, indent=2)}")
319
 
320
- # Process valid incoming messages in the background
321
  if payload.get("typeWebhook") == "incomingMessageReceived":
322
  executor.submit(self._process_incoming_message, payload)
323
  else:
@@ -342,7 +345,6 @@ class WhatsAppBot:
342
  while True:
343
  task = self.task_queue.get()
344
  try:
345
- # Set context for the worker thread
346
  ThreadContext.set_context(task["chat_id"], task["message_id"])
347
  handler = getattr(self, f"_task_{task['type']}", None)
348
  if handler:
@@ -361,7 +363,6 @@ class WhatsAppBot:
361
  chat_id = payload["senderData"]["chatId"]
362
  message_id = payload["idMessage"]
363
 
364
- # ** CHAT RESTRICTION LOGIC **
365
  if chat_id not in self.config.ALLOWED_CHATS:
366
  self.logger.warning(f"Ignoring message from unauthorized chat ID: {chat_id}")
367
  return
@@ -371,35 +372,26 @@ class WhatsAppBot:
371
  message_data = payload.get("messageData", {})
372
  type_message = message_data.get("typeMessage")
373
 
374
- # --- Unified File & Text Extraction ---
375
- text = ""
376
- download_url = None
377
- is_image_file = False
378
 
379
- # Case 1 & 4: Image or Document with potential caption
380
  if type_message in ["imageMessage", "documentMessage"]:
381
  file_data = message_data.get("fileMessageData", {})
382
  mime_type = file_data.get("mimeType", "")
383
-
384
- if type_message == "imageMessage" or (type_message == "documentMessage" and mime_type.startswith("image/")):
385
  is_image_file = True
386
  download_url = file_data.get("downloadUrl")
387
  text = file_data.get("caption", "").strip()
388
  if download_url:
389
- self.logger.debug(f"Caching file message {message_id} from {chat_id}")
390
  self.image_cache[chat_id].append((message_id, download_url))
391
 
392
- # Case 2 & 3: Reply to something (quoted message)
393
  elif type_message == "quotedMessage":
394
  text = message_data.get("extendedTextMessageData", {}).get("text", "").strip()
395
-
396
- # Fallback for other text types
397
  elif type_message == "textMessage":
398
  text = message_data.get("textMessageData", {}).get("textMessage", "").strip()
399
  elif type_message == "extendedTextMessage":
400
  text = message_data.get("extendedTextMessageData", {}).get("text", "").strip()
401
 
402
- # --- Action Dispatching ---
403
  if not text:
404
  self.logger.debug(f"Received message type '{type_message}' without actionable text from {chat_id}.")
405
  return
@@ -407,24 +399,18 @@ class WhatsAppBot:
407
  self.logger.info(f"Processing text from {chat_id}: '{text}'")
408
  self.conv_manager.add_user_message(chat_id, text)
409
 
410
- # If it's a file with a caption, check if the caption is an edit command
411
  if is_image_file and text:
412
  intent = self.intent_router.get_intent(text, chat_id)
413
  if isinstance(intent, EditImageIntent):
414
  self.logger.info("Detected direct edit command in image caption.")
415
  self.task_queue.put({
416
- "type": "edit_image",
417
- "chat_id": chat_id,
418
- "message_id": message_id,
419
- "prompt": intent.prompt,
420
- "download_url": download_url
421
  })
422
- return # Task dispatched, we are done.
423
 
424
- # Handle slash commands
425
  if text.startswith('/'):
426
  self._handle_command(chat_id, message_id, text, payload)
427
- # Handle natural language replies or regular chat
428
  else:
429
  self._handle_natural_language(chat_id, message_id, text, payload)
430
 
@@ -452,7 +438,7 @@ class WhatsAppBot:
452
  )
453
  self.api_client.send_message(chat_id, help_text, message_id)
454
  elif command == "/gen":
455
- self.task_queue.put({"type": "generate_image", "chat_id": chat_id, "message_id": message_id, "prompt": args})
456
  elif command == "/edit":
457
  self._dispatch_edit_image(chat_id, message_id, args, payload)
458
  elif command == "/joke":
@@ -467,128 +453,149 @@ class WhatsAppBot:
467
  def _handle_natural_language(self, chat_id, message_id, text, payload):
468
  """Processes natural language using the intent router."""
469
  intent = self.intent_router.get_intent(text, chat_id)
470
-
471
- task_data = {
472
- "chat_id": chat_id,
473
- "message_id": message_id,
474
- **intent.model_dump()
475
- }
476
 
477
  if intent.action == "edit_image":
478
- # This action needs the original payload to find the replied-to image
479
  self._dispatch_edit_image(chat_id, message_id, intent.prompt, payload)
480
  elif hasattr(self, f"_task_{intent.action}"):
481
- # Enqueue the task with its type and all necessary data
482
  self.task_queue.put({"type": intent.action, **task_data})
483
  else:
484
  self.logger.warning(f"No handler found for intent action: {intent.action}")
485
  self.api_client.send_message(chat_id, "Sorry, I'm not sure how to handle that.", message_id)
486
 
487
  def _dispatch_edit_image(self, chat_id, message_id, prompt, payload):
488
- """Checks for a replied-to image and dispatches the edit task."""
489
  message_data = payload.get("messageData", {})
490
-
491
- # This function is only for replies, so the top-level type must be quotedMessage
492
  if message_data.get("typeMessage") != "quotedMessage":
493
  self.api_client.send_message(chat_id, "To edit an image, please reply to an image with your instructions.", message_id)
494
  return
495
 
496
- quoted_info = message_data.get("quotedMessage")
497
- if not quoted_info:
498
- self.api_client.send_message(chat_id, "Couldn't find the message you replied to.", message_id)
499
- return
500
-
501
- # Check if the replied-to message is an image OR a document
502
- quoted_type = quoted_info.get("typeMessage")
503
- if quoted_type not in ["imageMessage", "documentMessage"]:
504
- self.api_client.send_message(chat_id, "You must reply to an image to edit it.", message_id)
505
- return
506
-
507
- # Get the ID of the message being replied to
508
  quoted_message_id = quoted_info.get("stanzaId")
509
  if not quoted_message_id:
510
  self.logger.error(f"Could not find stanzaId in quoted message: {quoted_info}")
511
  self.api_client.send_message(chat_id, "Sorry, I couldn't identify which image to edit.", message_id)
512
  return
513
 
514
- # Find the downloadUrl from our cache
515
- download_url = None
516
- if chat_id in self.image_cache:
517
- # Search the cache for the matching message ID
518
- for msg_id, url in self.image_cache[chat_id]:
519
  if msg_id == quoted_message_id:
520
- download_url = url
 
 
 
 
521
  break
522
 
523
- if not download_url:
524
- self.logger.warning(f"Could not find download URL for message ID {quoted_message_id} in cache for chat {chat_id}.")
 
 
 
 
 
 
 
 
525
  self.api_client.send_message(chat_id, "I couldn't find the original image. It might be too old. Please send it again before editing.", message_id)
526
  return
527
 
528
- self.logger.info(f"Found cached image URL for message {quoted_message_id}. Dispatching edit task.")
529
  self.task_queue.put({
530
- "type": "edit_image",
531
- "chat_id": chat_id,
532
- "message_id": message_id,
533
- "prompt": prompt,
534
- "download_url": download_url
535
  })
536
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  # --- Task Handler Methods ---
538
 
539
  def _task_send_text(self, task: Dict[str, Any]):
540
  chat_id, message_id, message = task["chat_id"], task["message_id"], task["message"]
541
  self.api_client.send_message(chat_id, message, message_id)
542
  self.conv_manager.add_bot_message(chat_id, message)
543
- # Asynchronously generate a voice reply for the sent text
544
  self.task_queue.put({"type": "voice_reply", "chat_id": chat_id, "message_id": message_id, "text": message})
545
 
546
  def _task_generate_image(self, task: Dict[str, Any]):
547
- chat_id, mid, prompt, count = task["chat_id"], task["message_id"], task["prompt"], task.get("count", 1)
 
548
  self.api_client.send_message(chat_id, f"🎨 Generating {count} image(s) for: \"{prompt}\"...", mid)
549
 
550
  for i in range(count):
 
551
  try:
552
- _, path, _, url = generate_image(prompt, mid, str(i), self.config.IMAGE_DIR, width=task.get("width"), height=task.get("height"))
553
  caption = f"✨ Image {i+1}/{count}: {prompt}"
554
- self.api_client.send_file(chat_id, path, caption, mid)
555
- os.remove(path)
 
 
 
 
 
 
 
556
  except Exception as e:
557
  self.logger.error(f"Image generation {i+1} failed: {e}", exc_info=True)
558
  self.api_client.send_message(chat_id, f"😒 Failed to generate image {i+1}.", mid)
 
559
 
560
  def _task_edit_image(self, task: Dict[str, Any]):
561
- chat_id, mid, prompt, url = task["chat_id"], task["message_id"], task["prompt"], task["download_url"]
 
562
  self.api_client.send_message(chat_id, f"🎨 Editing image with prompt: \"{prompt}\"...", mid)
563
 
564
- input_path, output_path = None, None
565
  try:
566
- image_data = self.api_client.download_file(url)
567
- if not image_data:
568
- raise ValueError("Failed to download image.")
569
-
570
  os.makedirs(self.config.TEMP_DIR, exist_ok=True)
571
- input_path = os.path.join(self.config.TEMP_DIR, f"input_{mid}.jpg")
572
  output_path = os.path.join(self.config.TEMP_DIR, f"output_{mid}.jpg")
573
-
574
- with open(input_path, 'wb') as f:
575
- f.write(image_data)
576
-
577
- flux_kontext_lib.generate_image(prompt, input_path, download_path=output_path)
 
 
 
 
 
 
 
 
578
 
579
  if os.path.exists(output_path):
580
  caption = f"✨ Edited: {prompt}"
581
- self.api_client.send_file(chat_id, output_path, caption, mid)
 
 
 
 
 
 
 
582
  else:
583
  raise ValueError("Edited image file not found.")
584
-
585
  except Exception as e:
586
  self.logger.error(f"Image editing task failed: {e}", exc_info=True)
587
  self.api_client.send_message(chat_id, "😒 Sorry, I failed to edit the image.", mid)
 
588
  finally:
589
- for path in [input_path, output_path]:
590
- if path and os.path.exists(path):
591
- os.remove(path)
592
 
593
  def _task_voice_reply(self, task: Dict[str, Any]):
594
  text = task["text"]
@@ -635,7 +642,6 @@ class WhatsAppBot:
635
  os.makedirs(d, exist_ok=True)
636
  self.logger.debug(f"Ensured directory exists: {d}")
637
 
638
- # Send a startup message to one of the allowed chats to confirm it's online
639
  self.api_client.send_message(
640
641
  "🌟 Eve is online and ready to help! Type /help to see commands."
@@ -651,7 +657,6 @@ if __name__ == "__main__":
651
  bot = WhatsAppBot(bot_config)
652
  bot.run()
653
  except ValueError as e:
654
- # Catch config validation errors
655
  logging.basicConfig()
656
  logging.getLogger().critical(f"❌ CONFIGURATION ERROR: {e}")
657
  except KeyboardInterrupt:
 
5
  Features include image generation, image editing, voice replies,
6
  and various utility functions, all handled by an asynchronous task queue.
7
  Includes request logging and chat ID filtering.
8
+ Version 2.1.0: Added caching for bot-sent images to allow for editing via replies.
9
  """
10
 
11
  import os
 
300
  self.conv_manager = ConversationManager(config.MAX_HISTORY_SIZE)
301
  self.intent_router = IntentRouter(self.conv_manager, self.logger)
302
  self.task_queue = queue.Queue()
303
+
304
+ # Cache for user-sent image message IDs and their download URLs
305
  self.image_cache = defaultdict(lambda: deque(maxlen=50))
306
+ # NEW: Cache for bot-sent image message IDs and their local file paths
307
+ self.sent_image_path_cache = defaultdict(lambda: deque(maxlen=50))
308
+
309
+ self.fastapi_app = FastAPI(title="WhatsApp Eve Bot", version="2.1.0")
310
  self._setup_routes()
311
  self._start_workers()
312
 
 
319
 
320
  try:
321
  payload = await request.json()
 
322
  self.logger.debug(f"Incoming webhook payload: {json.dumps(payload, indent=2)}")
323
 
 
324
  if payload.get("typeWebhook") == "incomingMessageReceived":
325
  executor.submit(self._process_incoming_message, payload)
326
  else:
 
345
  while True:
346
  task = self.task_queue.get()
347
  try:
 
348
  ThreadContext.set_context(task["chat_id"], task["message_id"])
349
  handler = getattr(self, f"_task_{task['type']}", None)
350
  if handler:
 
363
  chat_id = payload["senderData"]["chatId"]
364
  message_id = payload["idMessage"]
365
 
 
366
  if chat_id not in self.config.ALLOWED_CHATS:
367
  self.logger.warning(f"Ignoring message from unauthorized chat ID: {chat_id}")
368
  return
 
372
  message_data = payload.get("messageData", {})
373
  type_message = message_data.get("typeMessage")
374
 
375
+ text, download_url, is_image_file = "", None, False
 
 
 
376
 
 
377
  if type_message in ["imageMessage", "documentMessage"]:
378
  file_data = message_data.get("fileMessageData", {})
379
  mime_type = file_data.get("mimeType", "")
380
+ if type_message == "imageMessage" or mime_type.startswith("image/"):
 
381
  is_image_file = True
382
  download_url = file_data.get("downloadUrl")
383
  text = file_data.get("caption", "").strip()
384
  if download_url:
385
+ self.logger.debug(f"Caching incoming image message {message_id} from {chat_id}")
386
  self.image_cache[chat_id].append((message_id, download_url))
387
 
 
388
  elif type_message == "quotedMessage":
389
  text = message_data.get("extendedTextMessageData", {}).get("text", "").strip()
 
 
390
  elif type_message == "textMessage":
391
  text = message_data.get("textMessageData", {}).get("textMessage", "").strip()
392
  elif type_message == "extendedTextMessage":
393
  text = message_data.get("extendedTextMessageData", {}).get("text", "").strip()
394
 
 
395
  if not text:
396
  self.logger.debug(f"Received message type '{type_message}' without actionable text from {chat_id}.")
397
  return
 
399
  self.logger.info(f"Processing text from {chat_id}: '{text}'")
400
  self.conv_manager.add_user_message(chat_id, text)
401
 
 
402
  if is_image_file and text:
403
  intent = self.intent_router.get_intent(text, chat_id)
404
  if isinstance(intent, EditImageIntent):
405
  self.logger.info("Detected direct edit command in image caption.")
406
  self.task_queue.put({
407
+ "type": "edit_image", "chat_id": chat_id, "message_id": message_id,
408
+ "prompt": intent.prompt, "input_source": download_url, "source_type": 'url',
 
 
 
409
  })
410
+ return
411
 
 
412
  if text.startswith('/'):
413
  self._handle_command(chat_id, message_id, text, payload)
 
414
  else:
415
  self._handle_natural_language(chat_id, message_id, text, payload)
416
 
 
438
  )
439
  self.api_client.send_message(chat_id, help_text, message_id)
440
  elif command == "/gen":
441
+ self.task_queue.put({"type": "generate_image", "chat_id": chat_id, "message_id": message_id, "prompt": args, "count": self.config.DEFAULT_IMAGE_COUNT})
442
  elif command == "/edit":
443
  self._dispatch_edit_image(chat_id, message_id, args, payload)
444
  elif command == "/joke":
 
453
  def _handle_natural_language(self, chat_id, message_id, text, payload):
454
  """Processes natural language using the intent router."""
455
  intent = self.intent_router.get_intent(text, chat_id)
456
+ task_data = {"chat_id": chat_id, "message_id": message_id, **intent.model_dump()}
 
 
 
 
 
457
 
458
  if intent.action == "edit_image":
 
459
  self._dispatch_edit_image(chat_id, message_id, intent.prompt, payload)
460
  elif hasattr(self, f"_task_{intent.action}"):
 
461
  self.task_queue.put({"type": intent.action, **task_data})
462
  else:
463
  self.logger.warning(f"No handler found for intent action: {intent.action}")
464
  self.api_client.send_message(chat_id, "Sorry, I'm not sure how to handle that.", message_id)
465
 
466
  def _dispatch_edit_image(self, chat_id, message_id, prompt, payload):
467
+ """Finds a replied-to image from cache and dispatches the edit task."""
468
  message_data = payload.get("messageData", {})
 
 
469
  if message_data.get("typeMessage") != "quotedMessage":
470
  self.api_client.send_message(chat_id, "To edit an image, please reply to an image with your instructions.", message_id)
471
  return
472
 
473
+ quoted_info = message_data.get("quotedMessage", {})
 
 
 
 
 
 
 
 
 
 
 
474
  quoted_message_id = quoted_info.get("stanzaId")
475
  if not quoted_message_id:
476
  self.logger.error(f"Could not find stanzaId in quoted message: {quoted_info}")
477
  self.api_client.send_message(chat_id, "Sorry, I couldn't identify which image to edit.", message_id)
478
  return
479
 
480
+ input_source, source_type = None, None
481
+
482
+ # 1. Check cache for bot-sent images (local paths)
483
+ if chat_id in self.sent_image_path_cache:
484
+ for msg_id, path in reversed(self.sent_image_path_cache[chat_id]):
485
  if msg_id == quoted_message_id:
486
+ if os.path.exists(path):
487
+ input_source, source_type = path, 'path'
488
+ self.logger.info(f"Found replied-to image in local path cache: {path}")
489
+ else:
490
+ self.logger.warning(f"Found path for message {msg_id} but file missing: {path}")
491
  break
492
 
493
+ # 2. If not found, check cache for user-sent images (URLs)
494
+ if not input_source and chat_id in self.image_cache:
495
+ for msg_id, url in reversed(self.image_cache[chat_id]):
496
+ if msg_id == quoted_message_id:
497
+ input_source, source_type = url, 'url'
498
+ self.logger.info(f"Found replied-to image in URL cache: {url}")
499
+ break
500
+
501
+ if not input_source:
502
+ self.logger.warning(f"Could not find any image source for message ID {quoted_message_id} in cache for chat {chat_id}.")
503
  self.api_client.send_message(chat_id, "I couldn't find the original image. It might be too old. Please send it again before editing.", message_id)
504
  return
505
 
506
+ self.logger.info(f"Dispatching edit task for message {quoted_message_id} with source type '{source_type}'.")
507
  self.task_queue.put({
508
+ "type": "edit_image", "chat_id": chat_id, "message_id": message_id,
509
+ "prompt": prompt, "input_source": input_source, "source_type": source_type,
 
 
 
510
  })
511
 
512
+ def _manage_cached_file(self, chat_id: str, message_id: str, file_path: str):
513
+ """Adds a file to the cache and cleans up the oldest file if the cache is full."""
514
+ # Check if the cache is full before adding a new item
515
+ if len(self.sent_image_path_cache[chat_id]) == self.sent_image_path_cache[chat_id].maxlen:
516
+ old_id, old_path = self.sent_image_path_cache[chat_id][0] # Oldest item is at the left
517
+ if os.path.exists(old_path):
518
+ self.logger.debug(f"Cache full. Removing oldest cached image file: {old_path}")
519
+ try:
520
+ os.remove(old_path)
521
+ except OSError as e:
522
+ self.logger.error(f"Error removing old cached file {old_path}: {e}")
523
+
524
+ self.sent_image_path_cache[chat_id].append((message_id, file_path))
525
+
526
  # --- Task Handler Methods ---
527
 
528
  def _task_send_text(self, task: Dict[str, Any]):
529
  chat_id, message_id, message = task["chat_id"], task["message_id"], task["message"]
530
  self.api_client.send_message(chat_id, message, message_id)
531
  self.conv_manager.add_bot_message(chat_id, message)
 
532
  self.task_queue.put({"type": "voice_reply", "chat_id": chat_id, "message_id": message_id, "text": message})
533
 
534
  def _task_generate_image(self, task: Dict[str, Any]):
535
+ chat_id, mid, prompt = task["chat_id"], task["message_id"], task["prompt"]
536
+ count = task.get("count", self.config.DEFAULT_IMAGE_COUNT)
537
  self.api_client.send_message(chat_id, f"🎨 Generating {count} image(s) for: \"{prompt}\"...", mid)
538
 
539
  for i in range(count):
540
+ path = None
541
  try:
542
+ _, path, _, _ = generate_image(prompt, mid, str(i), self.config.IMAGE_DIR, width=task.get("width"), height=task.get("height"))
543
  caption = f"✨ Image {i+1}/{count}: {prompt}"
544
+ response = self.api_client.send_file(chat_id, path, caption, mid)
545
+
546
+ if response and response.get("idMessage"):
547
+ sent_message_id = response["idMessage"]
548
+ self.logger.info(f"Caching sent image path for message {sent_message_id}: {path}")
549
+ self._manage_cached_file(chat_id, sent_message_id, path)
550
+ else:
551
+ self.logger.warning(f"Could not get sent message ID for {path}. Deleting file.")
552
+ os.remove(path)
553
  except Exception as e:
554
  self.logger.error(f"Image generation {i+1} failed: {e}", exc_info=True)
555
  self.api_client.send_message(chat_id, f"😒 Failed to generate image {i+1}.", mid)
556
+ if path and os.path.exists(path): os.remove(path)
557
 
558
  def _task_edit_image(self, task: Dict[str, Any]):
559
+ chat_id, mid, prompt = task["chat_id"], task["message_id"], task["prompt"]
560
+ input_source, source_type = task["input_source"], task["source_type"]
561
  self.api_client.send_message(chat_id, f"🎨 Editing image with prompt: \"{prompt}\"...", mid)
562
 
563
+ input_path_temp, output_path = None, None
564
  try:
 
 
 
 
565
  os.makedirs(self.config.TEMP_DIR, exist_ok=True)
 
566
  output_path = os.path.join(self.config.TEMP_DIR, f"output_{mid}.jpg")
567
+
568
+ if source_type == 'url':
569
+ image_data = self.api_client.download_file(input_source)
570
+ if not image_data: raise ValueError(f"Failed to download image from URL: {input_source}")
571
+ input_path_temp = os.path.join(self.config.TEMP_DIR, f"input_{mid}.jpg")
572
+ with open(input_path_temp, 'wb') as f: f.write(image_data)
573
+ edit_input_path = input_path_temp
574
+ elif source_type == 'path':
575
+ edit_input_path = input_source
576
+ else:
577
+ raise ValueError(f"Unknown source_type: {source_type}")
578
+
579
+ flux_kontext_lib.generate_image(prompt, edit_input_path, download_path=output_path)
580
 
581
  if os.path.exists(output_path):
582
  caption = f"✨ Edited: {prompt}"
583
+ response = self.api_client.send_file(chat_id, output_path, caption, mid)
584
+ if response and response.get("idMessage"):
585
+ sent_message_id = response["idMessage"]
586
+ self.logger.info(f"Caching sent image path for message {sent_message_id}: {output_path}")
587
+ self._manage_cached_file(chat_id, sent_message_id, output_path)
588
+ else:
589
+ self.logger.warning(f"Could not get sent message ID for {output_path}. Deleting file.")
590
+ os.remove(output_path)
591
  else:
592
  raise ValueError("Edited image file not found.")
 
593
  except Exception as e:
594
  self.logger.error(f"Image editing task failed: {e}", exc_info=True)
595
  self.api_client.send_message(chat_id, "😒 Sorry, I failed to edit the image.", mid)
596
+ if output_path and os.path.exists(output_path): os.remove(output_path)
597
  finally:
598
+ if input_path_temp and os.path.exists(input_path_temp): os.remove(input_path_temp)
 
 
599
 
600
  def _task_voice_reply(self, task: Dict[str, Any]):
601
  text = task["text"]
 
642
  os.makedirs(d, exist_ok=True)
643
  self.logger.debug(f"Ensured directory exists: {d}")
644
 
 
645
  self.api_client.send_message(
646
647
  "🌟 Eve is online and ready to help! Type /help to see commands."
 
657
  bot = WhatsAppBot(bot_config)
658
  bot.run()
659
  except ValueError as e:
 
660
  logging.basicConfig()
661
  logging.getLogger().critical(f"❌ CONFIGURATION ERROR: {e}")
662
  except KeyboardInterrupt: