Chandima Prabhath commited on
Commit
21738c6
Β·
1 Parent(s): 00f979e

update debugs

Browse files
Files changed (2) hide show
  1. app.py +90 -33
  2. polLLM.py +1 -1
app.py CHANGED
@@ -4,6 +4,7 @@ Author: Assistant
4
  Description: A comprehensive WhatsApp bot with a professional, class-based structure.
5
  Features include image generation, image editing, voice replies,
6
  and various utility functions, all handled by an asynchronous task queue.
 
7
  """
8
 
9
  import os
@@ -13,12 +14,12 @@ import logging
13
  import queue
14
  import json
15
  import base64
16
- from typing import List, Optional, Union, Literal, Dict, Any, Tuple
17
  from collections import defaultdict, deque
18
  from concurrent.futures import ThreadPoolExecutor
19
 
20
  from fastapi import FastAPI, Request, HTTPException
21
- from fastapi.responses import JSONResponse, PlainTextResponse
22
  from pydantic import BaseModel, Field, ValidationError
23
  import uvicorn
24
 
@@ -43,14 +44,20 @@ class BotConfig:
43
  DEFAULT_IMAGE_COUNT: int = 4
44
  MAX_HISTORY_SIZE: int = 20
45
  WORKER_THREADS: int = 4
46
- LOG_LEVEL: str = "DEBUG"
 
 
 
 
47
 
48
  def __init__(self):
 
49
  self.GREEN_API_URL = os.getenv("GREEN_API_URL")
50
  self.GREEN_API_TOKEN = os.getenv("GREEN_API_TOKEN")
51
  self.GREEN_API_ID_INSTANCE = os.getenv("GREEN_API_ID_INSTANCE")
52
  self.WEBHOOK_AUTH_TOKEN = os.getenv("WEBHOOK_AUTH_TOKEN")
53
- self.LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
 
54
  self._validate()
55
 
56
  def _validate(self):
@@ -69,19 +76,31 @@ class LoggerSetup:
69
  """Sets up and manages structured logging for the application."""
70
  @staticmethod
71
  def setup(level: str) -> logging.Logger:
 
 
 
 
 
 
 
72
  logger = logging.getLogger("whatsapp_bot")
73
  logger.setLevel(level)
74
- logger.handlers.clear()
 
 
 
75
 
76
  handler = logging.StreamHandler()
 
77
  formatter = logging.Formatter(
78
- "%(asctime)s [%(levelname)s] [%(chat_id)s] %(funcName)s:%(lineno)d - %(message)s"
79
  )
80
  handler.setFormatter(formatter)
81
 
82
  class ContextFilter(logging.Filter):
 
83
  def filter(self, record):
84
- record.chat_id = ThreadContext.get_context().get("chat_id", "-")
85
  return True
86
 
87
  handler.addFilter(ContextFilter())
@@ -96,11 +115,13 @@ class ThreadContext:
96
 
97
  @classmethod
98
  def set_context(cls, chat_id: str, message_id: str):
 
99
  cls._context.chat_id = chat_id
100
  cls._context.message_id = message_id
101
 
102
  @classmethod
103
  def get_context(cls) -> Dict[str, Optional[str]]:
 
104
  return {
105
  "chat_id": getattr(cls._context, "chat_id", None),
106
  "message_id": getattr(cls._context, "message_id", None),
@@ -143,6 +164,7 @@ class GreenApiClient:
143
  url = f"{self.base_url}/{endpoint}/{self.config.GREEN_API_TOKEN}"
144
  for attempt in range(3):
145
  try:
 
146
  response = self.session.request(method, url, timeout=20, **kwargs)
147
  response.raise_for_status()
148
  return response.json()
@@ -224,6 +246,7 @@ class IntentRouter:
224
  try:
225
  raw_response = generate_llm(system_prompt)
226
  except LLMBadRequestError:
 
227
  self.conv_manager.clear_history(chat_id)
228
  return SendTextIntent(action="send_text", message="Oops! Let's start fresh! 🌟")
229
 
@@ -252,14 +275,16 @@ class IntentRouter:
252
 
253
  def _parse_response(self, raw_response: str) -> BaseIntent:
254
  try:
255
- parsed = json.loads(raw_response)
 
 
256
  for model in self.INTENT_MODELS:
257
  try:
258
  return model.model_validate(parsed)
259
  except ValidationError:
260
  continue
261
  except json.JSONDecodeError:
262
- pass
263
 
264
  # Fallback for non-JSON or unparsable responses
265
  return SendTextIntent(action="send_text", message=raw_response)
@@ -282,14 +307,22 @@ class WhatsAppBot:
282
  @self.fastapi_app.post("/whatsapp")
283
  async def webhook(request: Request):
284
  if request.headers.get("Authorization") != f"Bearer {self.config.WEBHOOK_AUTH_TOKEN}":
 
285
  raise HTTPException(403, "Unauthorized")
286
 
287
- payload = await request.json()
288
- self.logger.debug(f"Incoming webhook: {json.dumps(payload)}")
 
 
289
 
290
- # Process valid incoming messages in the background
291
- if payload.get("typeWebhook") == "incomingMessageReceived":
292
- executor.submit(self._process_incoming_message, payload)
 
 
 
 
 
293
 
294
  return JSONResponse(content={"status": "received"})
295
 
@@ -307,13 +340,16 @@ class WhatsAppBot:
307
  while True:
308
  task = self.task_queue.get()
309
  try:
 
 
310
  handler = getattr(self, f"_task_{task['type']}", None)
311
  if handler:
 
312
  handler(task)
313
  else:
314
- self.logger.warning(f"Unknown task type: {task['type']}")
315
  except Exception as e:
316
- self.logger.error(f"Error processing task {task['type']}: {e}", exc_info=True)
317
  finally:
318
  self.task_queue.task_done()
319
 
@@ -322,6 +358,14 @@ class WhatsAppBot:
322
  try:
323
  chat_id = payload["senderData"]["chatId"]
324
  message_id = payload["idMessage"]
 
 
 
 
 
 
 
 
325
  ThreadContext.set_context(chat_id, message_id)
326
 
327
  message_data = payload.get("messageData", {})
@@ -335,8 +379,10 @@ class WhatsAppBot:
335
 
336
  text = text.strip()
337
  if not text:
 
338
  return
339
 
 
340
  self.conv_manager.add_user_message(chat_id, text)
341
 
342
  # Handle direct commands
@@ -346,6 +392,8 @@ class WhatsAppBot:
346
  # Handle natural language and replies
347
  self._handle_natural_language(chat_id, message_id, text, payload)
348
 
 
 
349
  except Exception as e:
350
  self.logger.error(f"Failed to process message payload: {e}", exc_info=True)
351
 
@@ -372,11 +420,11 @@ class WhatsAppBot:
372
  elif command == "/edit":
373
  self._dispatch_edit_image(chat_id, message_id, args, payload)
374
  elif command == "/joke":
375
- self._task_joke({"chat_id": chat_id, "message_id": message_id})
376
  elif command == "/inspire":
377
- self._task_inspire({"chat_id": chat_id, "message_id": message_id})
378
  elif command == "/weather":
379
- self._task_weather({"chat_id": chat_id, "message_id": message_id, "location": args})
380
  else:
381
  self.api_client.send_message(chat_id, "Unknown command. Type /help for options.", message_id)
382
 
@@ -394,6 +442,7 @@ class WhatsAppBot:
394
  # This action needs the original payload to find the replied-to image
395
  self._dispatch_edit_image(chat_id, message_id, intent.prompt, payload)
396
  elif hasattr(self, f"_task_{intent.action}"):
 
397
  self.task_queue.put({"type": intent.action, **task_data})
398
  else:
399
  self.logger.warning(f"No handler found for intent action: {intent.action}")
@@ -401,7 +450,7 @@ class WhatsAppBot:
401
 
402
  def _dispatch_edit_image(self, chat_id, message_id, prompt, payload):
403
  """Checks for a replied-to image and dispatches the edit task."""
404
- quoted_message = payload.get("messageData", {}).get("quotedMessage")
405
  if not quoted_message or quoted_message.get("typeMessage") != "imageMessage":
406
  self.api_client.send_message(chat_id, "To edit an image, please reply to it with your instructions.", message_id)
407
  return
@@ -421,6 +470,7 @@ class WhatsAppBot:
421
  chat_id, message_id, message = task["chat_id"], task["message_id"], task["message"]
422
  self.api_client.send_message(chat_id, message, message_id)
423
  self.conv_manager.add_bot_message(chat_id, message)
 
424
  self.task_queue.put({"type": "voice_reply", "chat_id": chat_id, "message_id": message_id, "text": message})
425
 
426
  def _task_generate_image(self, task: Dict[str, Any]):
@@ -434,7 +484,7 @@ class WhatsAppBot:
434
  self.api_client.send_file(chat_id, path, caption, mid)
435
  os.remove(path)
436
  except Exception as e:
437
- self.logger.error(f"Image generation {i+1} failed: {e}")
438
  self.api_client.send_message(chat_id, f"😒 Failed to generate image {i+1}.", mid)
439
 
440
  def _task_edit_image(self, task: Dict[str, Any]):
@@ -447,6 +497,7 @@ class WhatsAppBot:
447
  if not image_data:
448
  raise ValueError("Failed to download image.")
449
 
 
450
  input_path = os.path.join(self.config.TEMP_DIR, f"input_{mid}.jpg")
451
  output_path = os.path.join(self.config.TEMP_DIR, f"output_{mid}.jpg")
452
 
@@ -462,7 +513,7 @@ class WhatsAppBot:
462
  raise ValueError("Edited image file not found.")
463
 
464
  except Exception as e:
465
- self.logger.error(f"Image editing task failed: {e}")
466
  self.api_client.send_message(chat_id, "😒 Sorry, I failed to edit the image.", mid)
467
  finally:
468
  for path in [input_path, output_path]:
@@ -479,13 +530,14 @@ class WhatsAppBot:
479
  self.api_client.send_file(task["chat_id"], path, quoted_message_id=task["message_id"])
480
  os.remove(path)
481
  except Exception as e:
482
- self.logger.warning(f"Voice reply generation failed: {e}")
483
 
484
  def _task_joke(self, task: Dict[str, Any]):
485
  try:
486
  j = requests.get("https://official-joke-api.appspot.com/random_joke", timeout=5).json()
487
  joke = f"{j['setup']}\n\n{j['punchline']}"
488
  except Exception:
 
489
  joke = generate_llm("Tell me a short, clean joke.")
490
  self._task_send_text({"type": "send_text", **task, "message": f"πŸ˜„ {joke}"})
491
 
@@ -494,13 +546,16 @@ class WhatsAppBot:
494
  self._task_send_text({"type": "send_text", **task, "message": f"✨ {quote}"})
495
 
496
  def _task_weather(self, task: Dict[str, Any]):
497
- location = task.get("location", "New York")
 
 
 
498
  try:
499
  raw = requests.get(f"http://wttr.in/{location.replace(' ', '+')}?format=4", timeout=10).text
500
  report = generate_llm(f"Create a friendly weather report in Celsius from this data:\n\n{raw}")
501
  self._task_send_text({"type": "send_text", **task, "message": f"🌀️ Weather for {location}:\n{report}"})
502
  except Exception as e:
503
- self.logger.error(f"Weather task failed: {e}")
504
  self.api_client.send_message(task["chat_id"], "Sorry, I couldn't get the weather.", task["message_id"])
505
 
506
  def run(self):
@@ -508,27 +563,29 @@ class WhatsAppBot:
508
  self.logger.info("Starting Eve WhatsApp Bot...")
509
  for d in [self.config.IMAGE_DIR, self.config.AUDIO_DIR, self.config.TEMP_DIR]:
510
  os.makedirs(d, exist_ok=True)
511
- self.logger.info(f"Ensured directory exists: {d}")
512
 
 
513
  self.api_client.send_message(
514
515
  "🌟 Eve is online and ready to help! Type /help to see commands."
516
  )
517
 
518
- uvicorn.run(self.fastapi_app, host="0.0.0.0", port=7860)
519
 
520
 
521
  if __name__ == "__main__":
522
  try:
523
- config = BotConfig()
524
- executor = ThreadPoolExecutor(max_workers=config.WORKER_THREADS * 2)
525
- bot = WhatsAppBot(config)
526
  bot.run()
527
  except ValueError as e:
528
  # Catch config validation errors
529
- print(f"❌ CONFIGURATION ERROR: {e}")
 
530
  except KeyboardInterrupt:
531
  print("\nπŸ›‘ Bot stopped by user.")
532
  except Exception as e:
533
- print(f"❌ A fatal error occurred: {e}")
534
-
 
4
  Description: A comprehensive WhatsApp bot with a professional, class-based structure.
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
 
14
  import queue
15
  import json
16
  import base64
17
+ from typing import List, Optional, Union, Literal, Dict, Any, Tuple, Set
18
  from collections import defaultdict, deque
19
  from concurrent.futures import ThreadPoolExecutor
20
 
21
  from fastapi import FastAPI, Request, HTTPException
22
+ from fastapi.responses import JSONResponse
23
  from pydantic import BaseModel, Field, ValidationError
24
  import uvicorn
25
 
 
44
  DEFAULT_IMAGE_COUNT: int = 4
45
  MAX_HISTORY_SIZE: int = 20
46
  WORKER_THREADS: int = 4
47
+ # Set log level to DEBUG to capture all incoming requests
48
+ LOG_LEVEL: str = "DEBUG"
49
+
50
+ # Whitelisted chat IDs. The bot will only respond to these chats.
51
+ ALLOWED_CHATS: Set[str] = {"[email protected]", "[email protected]"}
52
 
53
  def __init__(self):
54
+ """Initializes configuration from environment variables."""
55
  self.GREEN_API_URL = os.getenv("GREEN_API_URL")
56
  self.GREEN_API_TOKEN = os.getenv("GREEN_API_TOKEN")
57
  self.GREEN_API_ID_INSTANCE = os.getenv("GREEN_API_ID_INSTANCE")
58
  self.WEBHOOK_AUTH_TOKEN = os.getenv("WEBHOOK_AUTH_TOKEN")
59
+ # Allow overriding log level from environment
60
+ self.LOG_LEVEL = os.getenv("LOG_LEVEL", "DEBUG").upper()
61
  self._validate()
62
 
63
  def _validate(self):
 
76
  """Sets up and manages structured logging for the application."""
77
  @staticmethod
78
  def setup(level: str) -> logging.Logger:
79
+ """
80
+ Configures the root logger for the application.
81
+ Args:
82
+ level: The logging level (e.g., "DEBUG", "INFO").
83
+ Returns:
84
+ A configured logger instance.
85
+ """
86
  logger = logging.getLogger("whatsapp_bot")
87
  logger.setLevel(level)
88
+
89
+ # Avoid adding duplicate handlers
90
+ if logger.hasHandlers():
91
+ logger.handlers.clear()
92
 
93
  handler = logging.StreamHandler()
94
+ # Added a more detailed formatter
95
  formatter = logging.Formatter(
96
+ "%(asctime)s - %(name)s - [%(levelname)s] - [%(chat_id)s] - %(funcName)s:%(lineno)d - %(message)s"
97
  )
98
  handler.setFormatter(formatter)
99
 
100
  class ContextFilter(logging.Filter):
101
+ """Injects contextual information into log records."""
102
  def filter(self, record):
103
+ record.chat_id = ThreadContext.get_context().get("chat_id", "NO_CONTEXT")
104
  return True
105
 
106
  handler.addFilter(ContextFilter())
 
115
 
116
  @classmethod
117
  def set_context(cls, chat_id: str, message_id: str):
118
+ """Sets the context for the current thread."""
119
  cls._context.chat_id = chat_id
120
  cls._context.message_id = message_id
121
 
122
  @classmethod
123
  def get_context(cls) -> Dict[str, Optional[str]]:
124
+ """Retrieves the context for the current thread."""
125
  return {
126
  "chat_id": getattr(cls._context, "chat_id", None),
127
  "message_id": getattr(cls._context, "message_id", None),
 
164
  url = f"{self.base_url}/{endpoint}/{self.config.GREEN_API_TOKEN}"
165
  for attempt in range(3):
166
  try:
167
+ self.logger.debug(f"Sending API request to {url} with payload: {kwargs.get('json', kwargs.get('data'))}")
168
  response = self.session.request(method, url, timeout=20, **kwargs)
169
  response.raise_for_status()
170
  return response.json()
 
246
  try:
247
  raw_response = generate_llm(system_prompt)
248
  except LLMBadRequestError:
249
+ self.logger.warning(f"LLM request failed due to bad request for chat {chat_id}. Clearing history.")
250
  self.conv_manager.clear_history(chat_id)
251
  return SendTextIntent(action="send_text", message="Oops! Let's start fresh! 🌟")
252
 
 
275
 
276
  def _parse_response(self, raw_response: str) -> BaseIntent:
277
  try:
278
+ # Clean the response to ensure it's valid JSON
279
+ cleaned_response = raw_response.strip().replace("`json", "").replace("`", "")
280
+ parsed = json.loads(cleaned_response)
281
  for model in self.INTENT_MODELS:
282
  try:
283
  return model.model_validate(parsed)
284
  except ValidationError:
285
  continue
286
  except json.JSONDecodeError:
287
+ self.logger.warning(f"Could not decode LLM response to JSON: {raw_response}")
288
 
289
  # Fallback for non-JSON or unparsable responses
290
  return SendTextIntent(action="send_text", message=raw_response)
 
307
  @self.fastapi_app.post("/whatsapp")
308
  async def webhook(request: Request):
309
  if request.headers.get("Authorization") != f"Bearer {self.config.WEBHOOK_AUTH_TOKEN}":
310
+ self.logger.warning("Unauthorized webhook access attempt.")
311
  raise HTTPException(403, "Unauthorized")
312
 
313
+ try:
314
+ payload = await request.json()
315
+ # Log the entire incoming request payload for debugging
316
+ self.logger.debug(f"Incoming webhook payload: {json.dumps(payload, indent=2)}")
317
 
318
+ # Process valid incoming messages in the background
319
+ if payload.get("typeWebhook") == "incomingMessageReceived":
320
+ executor.submit(self._process_incoming_message, payload)
321
+ else:
322
+ self.logger.info(f"Received non-message webhook type: {payload.get('typeWebhook')}")
323
+ except json.JSONDecodeError:
324
+ self.logger.error("Failed to decode JSON from webhook request.")
325
+ raise HTTPException(400, "Invalid JSON payload.")
326
 
327
  return JSONResponse(content={"status": "received"})
328
 
 
340
  while True:
341
  task = self.task_queue.get()
342
  try:
343
+ # Set context for the worker thread
344
+ ThreadContext.set_context(task["chat_id"], task["message_id"])
345
  handler = getattr(self, f"_task_{task['type']}", None)
346
  if handler:
347
+ self.logger.debug(f"Processing task: {task['type']} for chat {task['chat_id']}")
348
  handler(task)
349
  else:
350
+ self.logger.warning(f"Unknown task type received: {task['type']}")
351
  except Exception as e:
352
+ self.logger.error(f"Error processing task {task.get('type', 'N/A')}: {e}", exc_info=True)
353
  finally:
354
  self.task_queue.task_done()
355
 
 
358
  try:
359
  chat_id = payload["senderData"]["chatId"]
360
  message_id = payload["idMessage"]
361
+
362
+ # ** CHAT RESTRICTION LOGIC **
363
+ # Check if the sender is in the allowed list
364
+ if chat_id not in self.config.ALLOWED_CHATS:
365
+ self.logger.warning(f"Ignoring message from unauthorized chat ID: {chat_id}")
366
+ return # Stop processing immediately
367
+
368
+ # Set thread context for logging
369
  ThreadContext.set_context(chat_id, message_id)
370
 
371
  message_data = payload.get("messageData", {})
 
379
 
380
  text = text.strip()
381
  if not text:
382
+ self.logger.debug(f"Received empty message from {chat_id}. Ignoring.")
383
  return
384
 
385
+ self.logger.info(f"Processing message from {chat_id}: '{text}'")
386
  self.conv_manager.add_user_message(chat_id, text)
387
 
388
  # Handle direct commands
 
392
  # Handle natural language and replies
393
  self._handle_natural_language(chat_id, message_id, text, payload)
394
 
395
+ except KeyError as e:
396
+ self.logger.error(f"Missing expected key in message payload: {e}. Payload: {payload}")
397
  except Exception as e:
398
  self.logger.error(f"Failed to process message payload: {e}", exc_info=True)
399
 
 
420
  elif command == "/edit":
421
  self._dispatch_edit_image(chat_id, message_id, args, payload)
422
  elif command == "/joke":
423
+ self.task_queue.put({"type": "joke", "chat_id": chat_id, "message_id": message_id})
424
  elif command == "/inspire":
425
+ self.task_queue.put({"type": "inspire", "chat_id": chat_id, "message_id": message_id})
426
  elif command == "/weather":
427
+ self.task_queue.put({"type": "weather", "chat_id": chat_id, "message_id": message_id, "location": args})
428
  else:
429
  self.api_client.send_message(chat_id, "Unknown command. Type /help for options.", message_id)
430
 
 
442
  # This action needs the original payload to find the replied-to image
443
  self._dispatch_edit_image(chat_id, message_id, intent.prompt, payload)
444
  elif hasattr(self, f"_task_{intent.action}"):
445
+ # Enqueue the task with its type and all necessary data
446
  self.task_queue.put({"type": intent.action, **task_data})
447
  else:
448
  self.logger.warning(f"No handler found for intent action: {intent.action}")
 
450
 
451
  def _dispatch_edit_image(self, chat_id, message_id, prompt, payload):
452
  """Checks for a replied-to image and dispatches the edit task."""
453
+ quoted_message = payload.get("messageData", {}).get("extendedTextMessageData", {}).get("quotedMessage")
454
  if not quoted_message or quoted_message.get("typeMessage") != "imageMessage":
455
  self.api_client.send_message(chat_id, "To edit an image, please reply to it with your instructions.", message_id)
456
  return
 
470
  chat_id, message_id, message = task["chat_id"], task["message_id"], task["message"]
471
  self.api_client.send_message(chat_id, message, message_id)
472
  self.conv_manager.add_bot_message(chat_id, message)
473
+ # Asynchronously generate a voice reply for the sent text
474
  self.task_queue.put({"type": "voice_reply", "chat_id": chat_id, "message_id": message_id, "text": message})
475
 
476
  def _task_generate_image(self, task: Dict[str, Any]):
 
484
  self.api_client.send_file(chat_id, path, caption, mid)
485
  os.remove(path)
486
  except Exception as e:
487
+ self.logger.error(f"Image generation {i+1} failed: {e}", exc_info=True)
488
  self.api_client.send_message(chat_id, f"😒 Failed to generate image {i+1}.", mid)
489
 
490
  def _task_edit_image(self, task: Dict[str, Any]):
 
497
  if not image_data:
498
  raise ValueError("Failed to download image.")
499
 
500
+ os.makedirs(self.config.TEMP_DIR, exist_ok=True)
501
  input_path = os.path.join(self.config.TEMP_DIR, f"input_{mid}.jpg")
502
  output_path = os.path.join(self.config.TEMP_DIR, f"output_{mid}.jpg")
503
 
 
513
  raise ValueError("Edited image file not found.")
514
 
515
  except Exception as e:
516
+ self.logger.error(f"Image editing task failed: {e}", exc_info=True)
517
  self.api_client.send_message(chat_id, "😒 Sorry, I failed to edit the image.", mid)
518
  finally:
519
  for path in [input_path, output_path]:
 
530
  self.api_client.send_file(task["chat_id"], path, quoted_message_id=task["message_id"])
531
  os.remove(path)
532
  except Exception as e:
533
+ self.logger.warning(f"Voice reply generation failed: {e}", exc_info=True)
534
 
535
  def _task_joke(self, task: Dict[str, Any]):
536
  try:
537
  j = requests.get("https://official-joke-api.appspot.com/random_joke", timeout=5).json()
538
  joke = f"{j['setup']}\n\n{j['punchline']}"
539
  except Exception:
540
+ self.logger.warning("Joke API failed, falling back to LLM.")
541
  joke = generate_llm("Tell me a short, clean joke.")
542
  self._task_send_text({"type": "send_text", **task, "message": f"πŸ˜„ {joke}"})
543
 
 
546
  self._task_send_text({"type": "send_text", **task, "message": f"✨ {quote}"})
547
 
548
  def _task_weather(self, task: Dict[str, Any]):
549
+ location = task.get("location")
550
+ if not location:
551
+ self.api_client.send_message(task["chat_id"], "Please provide a location for the weather.", task["message_id"])
552
+ return
553
  try:
554
  raw = requests.get(f"http://wttr.in/{location.replace(' ', '+')}?format=4", timeout=10).text
555
  report = generate_llm(f"Create a friendly weather report in Celsius from this data:\n\n{raw}")
556
  self._task_send_text({"type": "send_text", **task, "message": f"🌀️ Weather for {location}:\n{report}"})
557
  except Exception as e:
558
+ self.logger.error(f"Weather task failed: {e}", exc_info=True)
559
  self.api_client.send_message(task["chat_id"], "Sorry, I couldn't get the weather.", task["message_id"])
560
 
561
  def run(self):
 
563
  self.logger.info("Starting Eve WhatsApp Bot...")
564
  for d in [self.config.IMAGE_DIR, self.config.AUDIO_DIR, self.config.TEMP_DIR]:
565
  os.makedirs(d, exist_ok=True)
566
+ self.logger.debug(f"Ensured directory exists: {d}")
567
 
568
+ # Send a startup message to one of the allowed chats to confirm it's online
569
  self.api_client.send_message(
570
571
  "🌟 Eve is online and ready to help! Type /help to see commands."
572
  )
573
 
574
+ uvicorn.run(self.fastapi_app, host="0.0.0.0", port=7860, log_config=None)
575
 
576
 
577
  if __name__ == "__main__":
578
  try:
579
+ bot_config = BotConfig()
580
+ executor = ThreadPoolExecutor(max_workers=bot_config.WORKER_THREADS * 2)
581
+ bot = WhatsAppBot(bot_config)
582
  bot.run()
583
  except ValueError as e:
584
  # Catch config validation errors
585
+ logging.basicConfig()
586
+ logging.getLogger().critical(f"❌ CONFIGURATION ERROR: {e}")
587
  except KeyboardInterrupt:
588
  print("\nπŸ›‘ Bot stopped by user.")
589
  except Exception as e:
590
+ logging.basicConfig()
591
+ logging.getLogger().critical(f"❌ A fatal error occurred: {e}", exc_info=True)
polLLM.py CHANGED
@@ -11,7 +11,7 @@ load_dotenv()
11
  _config = read_config()["llm"]
12
 
13
  # --- Logging setup ---
14
- LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
15
  logger = logging.getLogger("polLLM")
16
  logger.setLevel(LOG_LEVEL)
17
  handler = logging.StreamHandler()
 
11
  _config = read_config()["llm"]
12
 
13
  # --- Logging setup ---
14
+ LOG_LEVEL = os.getenv("LOG_LEVEL", "DEBUG").upper()
15
  logger = logging.getLogger("polLLM")
16
  logger.setLevel(LOG_LEVEL)
17
  handler = logging.StreamHandler()