Chandima Prabhath commited on
Commit
71b0af4
Β·
1 Parent(s): e674ffd

Refactor app.py: streamline environment variable checks, enhance error handling, and improve command processing for better clarity and functionality.

Browse files
Files changed (1) hide show
  1. app.py +294 -180
app.py CHANGED
@@ -1,23 +1,19 @@
1
  import os
2
  import threading
3
- import time
4
- import random
5
  import logging
6
  import queue
7
  import re
8
  import json
9
- import requests
10
- from urllib.parse import urlparse
11
-
12
  from fastapi import FastAPI, Request, HTTPException
13
  from fastapi.responses import PlainTextResponse, JSONResponse
14
-
15
- from supabase import create_client, Client # pip install supabase
16
  from FLUX import generate_image
17
  from VoiceReply import generate_voice_reply
18
  from polLLM import generate_llm
19
 
20
- # β€”β€”β€”β€”β€” Configuration β€”β€”β€”β€”β€”
21
  logging.basicConfig(level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s")
22
 
23
  # Env vars
@@ -26,76 +22,58 @@ GREEN_API_MEDIA_URL = os.getenv("GREEN_API_MEDIA_URL", "https://api.green-api.
26
  GREEN_API_TOKEN = os.getenv("GREEN_API_TOKEN")
27
  GREEN_API_ID_INSTANCE = os.getenv("GREEN_API_ID_INSTANCE")
28
  WEBHOOK_AUTH_TOKEN = os.getenv("WEBHOOK_AUTH_TOKEN")
29
- BOT_GROUP_CHAT = os.getenv("BOT_GROUP_CHAT") # must be a group chat ID
30
- IMAGE_DIR = "/tmp/images"
31
- AUDIO_DIR = "/tmp/audio"
32
 
33
- # Supabase client (for data ops)
34
- SUPABASE_URL = os.getenv("SUPABASE_URL")
35
- SUPABASE_KEY = os.getenv("SUPABASE_KEY")
36
- supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
37
 
 
 
 
 
38
 
39
- if not all([GREEN_API_URL, GREEN_API_TOKEN, GREEN_API_ID_INSTANCE,
40
- WEBHOOK_AUTH_TOKEN, BOT_GROUP_CHAT,
41
- SUPABASE_URL, SUPABASE_KEY]):
42
- raise ValueError("One or more environment variables are not set properly")
43
 
44
- # Queues & stores
45
- task_queue = queue.Queue()
46
- trivia_store = {}
47
- polls = {}
48
  last_message_time = time.time()
49
 
50
- app = FastAPI()
51
-
52
- # β€”β€”β€”β€”β€” Inactivity Monitor β€”β€”β€”β€”β€”
53
  def inactivity_monitor():
54
  global last_message_time
55
  while True:
56
- time.sleep(60)
57
- if time.time() - last_message_time >= 300:
58
- send_message_to_chat(BOT_GROUP_CHAT, "⏰ I haven't heard from you in a while! I'm still here πŸ’–")
 
 
59
  last_message_time = time.time()
60
 
61
- # β€”β€”β€”β€”β€” Background Worker β€”β€”β€”β€”β€”
 
 
62
  def worker():
63
  while True:
64
  task = task_queue.get()
65
  try:
66
- if task["type"] == "image":
67
- handle_image_generation(task)
68
- elif task["type"] == "audio":
69
- response_audio(task["message_id"], task["chat_id"], task["prompt"])
 
 
 
70
  except Exception as e:
71
- logging.error(f"Error processing task {task}: {e}")
72
  finally:
73
  task_queue.task_done()
74
 
75
- # β€”β€”β€”β€”β€” Supabase Helpers β€”β€”β€”β€”β€”
76
- def ensure_user_exists(chat_id: str):
77
- """Create user record if not exists."""
78
- res = supabase.table("users") \
79
- .select("*") \
80
- .eq("chat_id", chat_id) \
81
- .maybe_single() \
82
- .execute()
83
- if res.data is None:
84
- supabase.table("users").insert({
85
- "chat_id": chat_id,
86
- "created_at": int(time.time())
87
- }).execute()
88
-
89
- def store_image_url(chat_id: str, prompt: str, url: str):
90
- supabase.table("images").insert({
91
- "chat_id": chat_id,
92
- "prompt": prompt,
93
- "url": url,
94
- "created_at": int(time.time())
95
- }).execute()
96
-
97
- # β€”β€”β€”β€”β€” Send Helpers β€”β€”β€”β€”β€”
98
- def send_message_to_chat(chat_id: str, message: str, retries=3):
99
  url = f"{GREEN_API_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendMessage/{GREEN_API_TOKEN}"
100
  payload = {"chatId": chat_id, "message": message}
101
  for i in range(retries):
@@ -103,12 +81,13 @@ def send_message_to_chat(chat_id: str, message: str, retries=3):
103
  r = requests.post(url, json=payload)
104
  r.raise_for_status()
105
  return r.json()
106
- except Exception as e:
107
- logging.error(f"send_message_to_chat attempt {i+1} failed: {e}")
108
- time.sleep(1)
109
- return {"error": "Failed after retries"}
110
 
111
- def send_message(message_id: str, chat_id: str, message: str, retries=3):
 
112
  url = f"{GREEN_API_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendMessage/{GREEN_API_TOKEN}"
113
  payload = {"chatId": chat_id, "message": message, "quotedMessageId": message_id}
114
  for i in range(retries):
@@ -116,215 +95,350 @@ def send_message(message_id: str, chat_id: str, message: str, retries=3):
116
  r = requests.post(url, json=payload)
117
  r.raise_for_status()
118
  return r.json()
119
- except Exception as e:
120
- logging.error(f"send_message attempt {i+1} failed: {e}")
121
- time.sleep(1)
122
- return {"error": "Failed after retries"}
123
 
124
- def send_image(message_id: str, chat_id: str, image_path: str, caption="Here you go!", retries=3):
 
125
  url = f"{GREEN_API_MEDIA_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendFileByUpload/{GREEN_API_TOKEN}"
126
  payload = {"chatId": chat_id, "caption": caption, "quotedMessageId": message_id}
 
127
  for i in range(retries):
128
  try:
129
- with open(image_path, "rb") as f:
130
- files = [("file", ("image.jpg", f, "image/jpeg"))]
131
- r = requests.post(url, data=payload, files=files)
132
- r.raise_for_status()
133
- return r.json()
134
- except Exception as e:
135
- logging.error(f"send_image attempt {i+1} failed: {e}")
136
- time.sleep(1)
137
- return {"error": "Failed after retries"}
138
-
139
- def send_audio(message_id: str, chat_id: str, audio_path: str, retries=3):
 
140
  url = f"{GREEN_API_MEDIA_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendFileByUpload/{GREEN_API_TOKEN}"
141
  payload = {"chatId": chat_id, "caption": "Here is your voice reply!", "quotedMessageId": message_id}
142
- for i in range(retries):
143
- try:
144
- with open(audio_path, "rb") as f:
145
- files = [("file", ("audio.mp3", f, "audio/mpeg"))]
146
- r = requests.post(url, data=payload, files=files)
147
- r.raise_for_status()
148
- return r.json()
149
- except Exception as e:
150
- logging.error(f"send_audio attempt {i+1} failed: {e}")
151
- time.sleep(1)
152
- return {"error": "Failed after retries"}
153
-
154
- # β€”β€”β€”β€”β€” Core Handlers β€”β€”β€”β€”β€”
 
 
155
  def response_text(message_id, chat_id, prompt):
156
  try:
157
  msg = generate_llm(prompt)
158
  send_message(message_id, chat_id, msg)
159
  except Exception:
160
- send_message(message_id, chat_id, "Sorry, something went wrong πŸ’¦")
161
 
162
  def response_audio(message_id, chat_id, prompt):
 
163
  try:
164
- result = generate_voice_reply(prompt, model="openai-audio", voice="coral", audio_dir=AUDIO_DIR)
165
  if result and result[0]:
166
  audio_path, _ = result
167
  send_audio(message_id, chat_id, audio_path)
168
- os.remove(audio_path)
 
169
  else:
170
  response_text(message_id, chat_id, prompt)
171
- except Exception:
172
- send_message(message_id, chat_id, "Error generating audio. Please try again later.")
173
-
174
- def handle_image_generation(task):
175
- message_id = task["message_id"]
176
- chat_id = task["chat_id"]
177
- prompt = task["prompt"]
178
 
179
- ensure_user_exists(chat_id)
180
-
181
- for attempt in range(4):
182
  try:
183
- img, path, ret_prompt, url = generate_image(prompt, message_id, message_id, IMAGE_DIR)
184
  if img:
185
- store_image_url(chat_id, prompt, url)
186
- formatted = "\n\n".join(f"_{p.strip()}_" for p in ret_prompt.split("\n\n") if p.strip())
187
- send_image(message_id, chat_id, path,
188
- caption=f"✨ Image ready: {url}\n{formatted}")
189
- return
 
 
 
 
190
  else:
191
- raise RuntimeError("generate_image returned no image")
192
  except Exception as e:
193
- logging.error(f"Image gen attempt {attempt+1} failed: {e}")
194
- if attempt < 3:
195
- time.sleep(5)
196
- else:
197
- send_message(message_id, chat_id, "😒 Sorry, I couldn't generate the image after several tries.")
198
 
199
- # β€”β€”β€”β€”β€” Startup β€”β€”β€”β€”β€”
200
  def send_startup_message():
201
- send_message_to_chat(BOT_GROUP_CHAT,
202
- "🌟 Hi! I'm Eve, your friendly AI assistant. I'm now live and ready to help with images, voice replies, and more!"
203
- )
 
 
 
 
204
 
205
- # β€”β€”β€”β€”β€” Help Text β€”β€”β€”β€”β€”
206
  help_text = (
207
- "πŸ€– *Hi there, I'm Eve!* Here are the commands you can use in this group:\n\n"
208
- "β€’ */help* – Show this help message.\n"
209
- "β€’ */summarize <text>* – Get a quick summary.\n"
210
- "β€’ */translate <lang>|<text>* – Translate text.\n"
211
- "β€’ */joke* – A random joke.\n"
212
- "β€’ */weather <location>* – Current weather.\n"
213
- "β€’ */inspire* – Inspirational quote.\n"
214
- "β€’ */trivia* – Start trivia.\n"
215
- "β€’ */answer* – Answer or reveal trivia.\n"
216
- "β€’ */meme <text>* – Generate a meme image.\n"
217
- "β€’ */poll Q|Opt1|Opt2|…* – Create a poll.\n"
218
- "β€’ */results* – Show poll results.\n"
219
- "β€’ */endpoll* – End the poll.\n"
220
- "β€’ */gen <prompt>* – Generate an image.\n\n"
221
- "Anything else I’ll reply with a voice message. πŸ’–"
222
- )
223
-
224
- # β€”β€”β€”β€”β€” Webhook β€”β€”β€”β€”β€”
225
  @app.post("/whatsapp")
226
  async def whatsapp_webhook(request: Request):
227
  global last_message_time
228
  last_message_time = time.time()
229
 
230
- if request.headers.get("Authorization", "") != f"Bearer {WEBHOOK_AUTH_TOKEN}":
 
231
  raise HTTPException(403, "Unauthorized")
232
-
233
- data = await request.json()
 
 
 
 
234
  if data.get("typeWebhook") != "incomingMessageReceived":
235
  return {"success": True}
236
 
 
237
  sd = data["senderData"]
238
- chat_id = sd["chatId"]
239
- mid = data["idMessage"]
240
-
241
- # only process messages from the BOT_GROUP_CHAT group
242
- if chat_id != BOT_GROUP_CHAT:
243
- return {"success": True}
244
 
245
  md = data.get("messageData", {})
246
- if "quotedMessage" in md:
 
247
  return {"success": True}
248
 
249
- # extract text
250
  if "textMessageData" in md:
251
  body = md["textMessageData"].get("textMessage", "").strip()
 
252
  elif "extendedTextMessageData" in md:
253
  body = md["extendedTextMessageData"].get("text", "").strip()
 
254
  else:
255
  return {"success": True}
256
 
 
 
 
 
 
257
  low = body.lower()
258
 
259
- # β€” Commands β€”
260
  if low == "/help":
261
- send_message(mid, chat_id, help_text)
262
  return {"success": True}
263
 
264
  if low.startswith("/summarize "):
265
  txt = body[len("/summarize "):].strip()
266
- send_message(mid, chat_id, generate_llm(f"Summarize:\n\n{txt}"))
 
267
  return {"success": True}
268
 
269
  if low.startswith("/translate "):
270
  part = body[len("/translate "):]
271
  if "|" not in part:
272
- send_message(mid, chat_id, "Use `/translate <lang>|<text>` please.")
273
  else:
274
- lang, txt = part.split("|",1)
275
- send_message(mid, chat_id,
276
- generate_llm(f"Translate into {lang.strip()}:\n\n{txt.strip()}"))
277
  return {"success": True}
278
 
279
  if low == "/joke":
280
  try:
281
  joke = requests.get("https://official-joke-api.appspot.com/random_joke", timeout=5).json()
282
- send_message(mid, chat_id, f"{joke['setup']}\n\n{joke['punchline']}")
283
  except:
284
- send_message(mid, chat_id, generate_llm("Tell me a short joke."))
285
  return {"success": True}
286
 
287
  if low.startswith("/weather "):
288
  loc = body[len("/weather "):].strip().replace(" ", "+")
289
  try:
290
  w = requests.get(f"http://sl.wttr.in/{loc}?format=4", timeout=5).text
291
- send_message(mid, chat_id, w)
292
  except:
293
- send_message(mid, chat_id, "Could not fetch weather.")
294
  return {"success": True}
295
 
296
  if low == "/inspire":
297
- send_message(mid, chat_id, f"✨ {generate_llm('Give me an inspirational quote.')}")
 
298
  return {"success": True}
299
 
300
- # … trivia, polls omitted for brevity …
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
  if low.startswith("/meme "):
303
- prompt = body[len("/meme "):].strip()
304
- send_message(mid, chat_id, "🎨 Generating your meme…")
305
- task_queue.put({"type":"image","message_id":mid,"chat_id":chat_id,"prompt":f"meme template: {prompt}"})
 
 
 
 
 
306
  return {"success": True}
307
 
308
- if low.startswith("/gen "):
309
- prompt = body[len("/gen "):].strip()
310
- send_message(mid, chat_id, "✨ Your image is being generated. Please wait…")
311
- task_queue.put({"type":"image","message_id":mid,"chat_id":chat_id,"prompt":prompt})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  return {"success": True}
313
 
314
- # fallback to voice
315
- task_queue.put({"type":"audio","message_id":mid,"chat_id":chat_id,"prompt":body})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  return {"success": True}
317
 
318
  @app.get("/", response_class=PlainTextResponse)
319
  def index():
320
- return "Eve is running! πŸ’–"
321
 
322
  if __name__ == "__main__":
323
- # 2) start background threads
324
- threading.Thread(target=inactivity_monitor, daemon=True).start()
325
- threading.Thread(target=worker, daemon=True).start()
326
-
327
- # 3) send startup message & run server
 
 
328
  send_startup_message()
329
  import uvicorn
330
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  import os
2
  import threading
3
+ import requests
 
4
  import logging
5
  import queue
6
  import re
7
  import json
8
+ import time
9
+ import random
 
10
  from fastapi import FastAPI, Request, HTTPException
11
  from fastapi.responses import PlainTextResponse, JSONResponse
 
 
12
  from FLUX import generate_image
13
  from VoiceReply import generate_voice_reply
14
  from polLLM import generate_llm
15
 
16
+ # Configure logging
17
  logging.basicConfig(level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s")
18
 
19
  # Env vars
 
22
  GREEN_API_TOKEN = os.getenv("GREEN_API_TOKEN")
23
  GREEN_API_ID_INSTANCE = os.getenv("GREEN_API_ID_INSTANCE")
24
  WEBHOOK_AUTH_TOKEN = os.getenv("WEBHOOK_AUTH_TOKEN")
25
+ BOT_GROUP_CHAT = "120363312903494448@g.us" # Chat ID for system messages
26
+ image_dir = "/tmp/images"
27
+ audio_dir = "/tmp/audio"
28
 
29
+ if not all([GREEN_API_URL, GREEN_API_TOKEN, GREEN_API_ID_INSTANCE, WEBHOOK_AUTH_TOKEN]):
30
+ raise ValueError("Environment variables are not set properly")
 
 
31
 
32
+ # Queues & in‑memory stores
33
+ task_queue = queue.Queue()
34
+ trivia_store = {} # chat_id β†’ {"question":…, "answer":…}
35
+ polls = {} # chat_id β†’ {"question":…, "options": […], "votes": {1: 0, ...}, "voters": {jid: opt}}
36
 
37
+ app = FastAPI()
 
 
 
38
 
39
+ # Global inactivity tracker
 
 
 
40
  last_message_time = time.time()
41
 
42
+ # --- Inactivity Monitor ---
 
 
43
  def inactivity_monitor():
44
  global last_message_time
45
  while True:
46
+ time.sleep(60) # check every minute
47
+ if time.time() - last_message_time >= 300: # 5 minutes inactivity
48
+ if BOT_GROUP_CHAT:
49
+ reminder = "⏰ I haven't heard from you in a while! I'm still here if you need anything."
50
+ send_message("inactivity", BOT_GROUP_CHAT, reminder)
51
  last_message_time = time.time()
52
 
53
+ threading.Thread(target=inactivity_monitor, daemon=True).start()
54
+
55
+ # --- Background Worker ---
56
  def worker():
57
  while True:
58
  task = task_queue.get()
59
  try:
60
+ typ = task["type"]
61
+ mid = task["message_id"]
62
+ cid = task["chat_id"]
63
+ if typ == "image":
64
+ handle_image_generation(mid, cid, task["prompt"])
65
+ elif typ == "audio":
66
+ response_audio(mid, cid, task["prompt"])
67
  except Exception as e:
68
+ logging.error(f"Error processing {task}: {e}")
69
  finally:
70
  task_queue.task_done()
71
 
72
+ threading.Thread(target=worker, daemon=True).start()
73
+
74
+ # --- send helpers ---
75
+ def send_message_to_chat(to_number, message, retries=3):
76
+ chat_id = to_number if to_number.endswith("@g.us") else to_number
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  url = f"{GREEN_API_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendMessage/{GREEN_API_TOKEN}"
78
  payload = {"chatId": chat_id, "message": message}
79
  for i in range(retries):
 
81
  r = requests.post(url, json=payload)
82
  r.raise_for_status()
83
  return r.json()
84
+ except requests.RequestException as e:
85
+ if i == retries - 1:
86
+ logging.error("send_message_to_chat failed: %s", str(e))
87
+ return {"error": str(e)}
88
 
89
+ def send_message(message_id, to_number, message, retries=3):
90
+ chat_id = to_number if to_number.endswith("@g.us") else to_number
91
  url = f"{GREEN_API_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendMessage/{GREEN_API_TOKEN}"
92
  payload = {"chatId": chat_id, "message": message, "quotedMessageId": message_id}
93
  for i in range(retries):
 
95
  r = requests.post(url, json=payload)
96
  r.raise_for_status()
97
  return r.json()
98
+ except requests.RequestException as e:
99
+ if i == retries - 1:
100
+ return {"error": str(e)}
 
101
 
102
+ def send_image(message_id, to_number, image_path, caption="Here you go!", retries=3):
103
+ chat_id = to_number if to_number.endswith("@g.us") else to_number
104
  url = f"{GREEN_API_MEDIA_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendFileByUpload/{GREEN_API_TOKEN}"
105
  payload = {"chatId": chat_id, "caption": caption, "quotedMessageId": message_id}
106
+ files = [("file", ("image.jpg", open(image_path, "rb"), "image/jpeg"))]
107
  for i in range(retries):
108
  try:
109
+ r = requests.post(url, data=payload, files=files)
110
+ r.raise_for_status()
111
+ return r.json()
112
+ except requests.RequestException as e:
113
+ if i == retries - 1:
114
+ return {"error": str(e)}
115
+
116
+ def send_audio(message_id, to_number, audio_path, retries=3):
117
+ logging.debug("send_audio")
118
+ chat_id = to_number if to_number.endswith("@g.us") else to_number
119
+ if not os.path.exists(audio_path):
120
+ logging.debug(f"Missing audio: {audio_path}")
121
  url = f"{GREEN_API_MEDIA_URL}/waInstance{GREEN_API_ID_INSTANCE}/sendFileByUpload/{GREEN_API_TOKEN}"
122
  payload = {"chatId": chat_id, "caption": "Here is your voice reply!", "quotedMessageId": message_id}
123
+ try:
124
+ with open(audio_path, "rb") as f:
125
+ files = [("file", ("audio.mp3", f, "audio/mpeg"))]
126
+ for i in range(retries):
127
+ try:
128
+ r = requests.post(url, data=payload, files=files)
129
+ r.raise_for_status()
130
+ return r.json()
131
+ except requests.RequestException as e:
132
+ if i == retries - 1:
133
+ return {"error": str(e)}
134
+ except Exception as e:
135
+ return {"error": str(e)}
136
+
137
+ # --- core response functions ---
138
  def response_text(message_id, chat_id, prompt):
139
  try:
140
  msg = generate_llm(prompt)
141
  send_message(message_id, chat_id, msg)
142
  except Exception:
143
+ send_message(message_id, chat_id, "Error processing your request.")
144
 
145
  def response_audio(message_id, chat_id, prompt):
146
+ logging.debug("response_audio prompt=%s", prompt)
147
  try:
148
+ result = generate_voice_reply(prompt, model="openai-audio", voice="coral", audio_dir=audio_dir)
149
  if result and result[0]:
150
  audio_path, _ = result
151
  send_audio(message_id, chat_id, audio_path)
152
+ if os.path.exists(audio_path):
153
+ os.remove(audio_path)
154
  else:
155
  response_text(message_id, chat_id, prompt)
156
+ except Exception as e:
157
+ logging.debug("audio error: %s", e)
158
+ send_message(message_id, chat_id, "Error generating audio. Try again later.")
 
 
 
 
159
 
160
+ def handle_image_generation(message_id, chat_id, prompt):
161
+ for i in range(4):
 
162
  try:
163
+ img, path, ret_prompt, url = generate_image(prompt, message_id, message_id, image_dir)
164
  if img:
165
+ formatted_ret_prompt = "\n\n".join(
166
+ f"_{paragraph.strip()}_" for paragraph in ret_prompt.split("\n\n") if paragraph.strip()
167
+ )
168
+ send_image(
169
+ message_id,
170
+ chat_id,
171
+ path,
172
+ caption=f"✨ Image ready: {url}\n>{chr(8203)} {formatted_ret_prompt}"
173
+ )
174
  else:
175
+ send_message(message_id, chat_id, "Image generation failed.")
176
  except Exception as e:
177
+ logging.error("Error in handle_image_generation: %s", e)
178
+ send_message(message_id, chat_id, "Error generating image.")
 
 
 
179
 
180
+ # --- Startup Message ---
181
  def send_startup_message():
182
+ if BOT_GROUP_CHAT:
183
+ startup_msg = "🌟 Hi! I'm Eve, your friendly AI assistant. I'm now live and ready to help with images, voice replies, and more!"
184
+ resp = send_message_to_chat(BOT_GROUP_CHAT, startup_msg)
185
+ if "error" in resp:
186
+ logging.error("Startup message failed: %s", resp["error"])
187
+ else:
188
+ logging.warning("BOT_GROUP_CHAT is not set; startup message not sent.")
189
 
 
190
  help_text = (
191
+ "πŸ€– *Hi there, I'm Eve!* Here are the commands you can use:\n\n"
192
+ "β€’ */help* – _Show this help message._\n"
193
+ "β€’ */summarize <text>* – _Get a quick summary of your text._\n"
194
+ "β€’ */translate <language>|<text>* – _Translate text to your chosen language._\n"
195
+ "β€’ */joke* – _Enjoy a random, funny joke._\n"
196
+ "β€’ */weather <location>* – _Get the current weather for a location._\n"
197
+ "β€’ */inspire* – _Receive a short inspirational quote._\n"
198
+ "β€’ */trivia* – _Start a new trivia question._\n"
199
+ "β€’ */answer [your answer]* – _Reveal the trivia answer or check your answer if provided._\n"
200
+ "β€’ */meme <text>* – _Generate a fun meme image._\n"
201
+ "β€’ */poll <Question>|<Option1>|<Option2>|…* – _Create a poll._\n"
202
+ "β€’ */results* – _See current poll results._\n"
203
+ "β€’ */endpoll* – _End the poll and show final results._\n"
204
+ "β€’ */gen <prompt>* – _Generate an image from your prompt._\n\n"
205
+ "Send any other text and I'll reply with a voice message. I'm here to help, so don't hesitate to ask!"
206
+ )
207
+
208
+ # --- Webhook ---
209
  @app.post("/whatsapp")
210
  async def whatsapp_webhook(request: Request):
211
  global last_message_time
212
  last_message_time = time.time()
213
 
214
+ auth = request.headers.get("Authorization", "").strip()
215
+ if auth != f"Bearer {WEBHOOK_AUTH_TOKEN}":
216
  raise HTTPException(403, "Unauthorized")
217
+ try:
218
+ data = await request.json()
219
+ chat_id = data["senderData"]["chatId"]
220
+ print(f"New message from chat ID: {chat_id}")
221
+ except:
222
+ return JSONResponse({"error": "Invalid JSON"}, status_code=400)
223
  if data.get("typeWebhook") != "incomingMessageReceived":
224
  return {"success": True}
225
 
226
+ logging.debug("recv: %s", data)
227
  sd = data["senderData"]
228
+ chat = sd["chatId"]
229
+ mid = data["idMessage"]
230
+ sender_jid = sd.get("sender")
 
 
 
231
 
232
  md = data.get("messageData", {})
233
+ if md.get("typeMessage") == "quotedMessage" or "quotedMessage" in md:
234
+ logging.debug("skip native quotedMessage")
235
  return {"success": True}
236
 
 
237
  if "textMessageData" in md:
238
  body = md["textMessageData"].get("textMessage", "").strip()
239
+ ctx = md["textMessageData"].get("contextInfo", {})
240
  elif "extendedTextMessageData" in md:
241
  body = md["extendedTextMessageData"].get("text", "").strip()
242
+ ctx = md["extendedTextMessageData"].get("contextInfo", {})
243
  else:
244
  return {"success": True}
245
 
246
+ if ctx.get("mentionedJid") or ctx.get("mentionedJidList"):
247
+ return {"success": True}
248
+ if chat.endswith("@g.us") and re.search(r"@\d+", body):
249
+ return {"success": True}
250
+
251
  low = body.lower()
252
 
253
+ # --- New Commands ---
254
  if low == "/help":
255
+ send_message(mid, chat, help_text)
256
  return {"success": True}
257
 
258
  if low.startswith("/summarize "):
259
  txt = body[len("/summarize "):].strip()
260
+ summary = generate_llm(f"Summarize this text in one short paragraph:\n\n{txt}")
261
+ send_message(mid, chat, summary)
262
  return {"success": True}
263
 
264
  if low.startswith("/translate "):
265
  part = body[len("/translate "):]
266
  if "|" not in part:
267
+ send_message(mid, chat, "Please use `/translate <language>|<text>`")
268
  else:
269
+ lang, txt = part.split("|", 1)
270
+ resp = generate_llm(f"Translate the following into {lang.strip()}:\n\n{txt.strip()}")
271
+ send_message(mid, chat, resp)
272
  return {"success": True}
273
 
274
  if low == "/joke":
275
  try:
276
  joke = requests.get("https://official-joke-api.appspot.com/random_joke", timeout=5).json()
277
+ send_message(mid, chat, f"{joke['setup']}\n\n{joke['punchline']}")
278
  except:
279
+ send_message(mid, chat, generate_llm("Tell me a short, funny joke."))
280
  return {"success": True}
281
 
282
  if low.startswith("/weather "):
283
  loc = body[len("/weather "):].strip().replace(" ", "+")
284
  try:
285
  w = requests.get(f"http://sl.wttr.in/{loc}?format=4", timeout=5).text
286
+ send_message(mid, chat, w)
287
  except:
288
+ send_message(mid, chat, "Could not fetch weather.")
289
  return {"success": True}
290
 
291
  if low == "/inspire":
292
+ quote = generate_llm("Give me a short inspirational quote.")
293
+ send_message(mid, chat, f"✨ {quote}")
294
  return {"success": True}
295
 
296
+ # TRIVIA
297
+ if low == "/trivia":
298
+ randomSeed = random.randint(0, 9999999)
299
+ raw = generate_llm(
300
+ (f"Generate a unique and random trivia question and answer based on this random seed {randomSeed} to make it unique in JSON format. "
301
+ "The output should strictly follow this example format without extra text:\n\n"
302
+ "{\"question\": \"What is the capital of France?\", \"answer\": \"Paris\"}")
303
+ )
304
+ def extract_json(text):
305
+ import re
306
+ match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL)
307
+ if match:
308
+ return match.group(1)
309
+ return text
310
+ try:
311
+ json_text = extract_json(raw)
312
+ obj = json.loads(json_text)
313
+ if "question" in obj and "answer" in obj:
314
+ trivia_store[chat] = obj
315
+ send_message(mid, chat, f"❓ {obj['question']}\nReply with `/answer` followed by your answer (if you want to check it) or just `/answer` to reveal the correct answer.")
316
+ else:
317
+ raise ValueError("Missing expected keys.")
318
+ except Exception as e:
319
+ logging.error("Trivia JSON parse error: %s, raw response: %s", e, raw)
320
+ send_message(mid, chat, "Failed to generate trivia. Please try again.")
321
+ return {"success": True}
322
+
323
+ # ANSWER: Accept any message starting with /answer. If additional text is provided, check it.
324
+ if low.startswith("/answer"):
325
+ # Remove command and any extra spaces
326
+ user_response = body[len("/answer"):].strip()
327
+ if chat in trivia_store:
328
+ correct_answer = trivia_store[chat]["answer"]
329
+ question = trivia_store[chat]["question"]
330
+ # If user provided an answer, evaluate it via LLM; otherwise, just reveal the answer.
331
+ if user_response:
332
+ eval_prompt = (
333
+ f"Question: {question}\n"
334
+ f"Correct Answer: {correct_answer}\n"
335
+ f"User Answer: {user_response}\n"
336
+ "Is the user's answer correct? Respond with 'Correct' if yes, or 'Incorrect' if not, and explain briefly."
337
+ )
338
+ verdict = generate_llm(eval_prompt)
339
+ send_message(mid, chat, f"πŸ’‘ {verdict}")
340
+ else:
341
+ send_message(mid, chat, f"πŸ’‘ Answer: {correct_answer}")
342
+ trivia_store.pop(chat, None)
343
+ else:
344
+ send_message(mid, chat, "No active trivia. Send `/trivia` to start one.")
345
+ return {"success": True}
346
 
347
  if low.startswith("/meme "):
348
+ txt = body[len("/meme "):].strip()
349
+ send_message(mid, chat, "🎨 Generating your meme...")
350
+ task_queue.put({
351
+ "type": "image",
352
+ "message_id": mid,
353
+ "chat_id": chat,
354
+ "prompt": f"meme template with text: {txt}"
355
+ })
356
  return {"success": True}
357
 
358
+ if low.startswith("/poll "):
359
+ parts = body[len("/poll "):].split("|")
360
+ if len(parts) < 3:
361
+ send_message(mid, chat, "Please use `/poll Question|Option1|Option2|...`")
362
+ else:
363
+ q = parts[0].strip()
364
+ opts = [p.strip() for p in parts[1:]]
365
+ votes = {i+1: 0 for i in range(len(opts))}
366
+ polls[chat] = {"question": q, "options": opts, "votes": votes, "voters": {}}
367
+ txt = f"πŸ“Š *Poll:* {q}\n" + "\n".join(
368
+ f"{i+1}. {opt}" for i, opt in enumerate(opts)
369
+ ) + "\n\nReply with the *option number* to vote."
370
+ send_message(mid, chat, txt)
371
+ return {"success": True}
372
+
373
+ if chat in polls and body.isdigit():
374
+ n = int(body)
375
+ p = polls[chat]
376
+ if 1 <= n <= len(p["options"]):
377
+ prev = p["voters"].get(sender_jid)
378
+ if prev:
379
+ p["votes"][prev] -= 1
380
+ p["votes"][n] += 1
381
+ p["voters"][sender_jid] = n
382
+ send_message(mid, chat, f"βœ… Vote recorded: {p['options'][n-1]}")
383
+ return {"success": True}
384
+
385
+ if low == "/results":
386
+ if chat in polls:
387
+ p = polls[chat]
388
+ txt = f"πŸ“Š *Results:* {p['question']}\n" + "\n".join(
389
+ f"{i}. {opt}: {p['votes'][i]}" for i, opt in enumerate([""] + p["options"]) if i > 0
390
+ )
391
+ send_message(mid, chat, txt)
392
+ else:
393
+ send_message(mid, chat, "No active poll.")
394
+ return {"success": True}
395
+
396
+ if low == "/endpoll":
397
+ if chat in polls:
398
+ p = polls.pop(chat)
399
+ txt = f"πŸ“Š *Final Results:* {p['question']}\n" + "\n".join(
400
+ f"{i}. {opt}: {p['votes'][i]}" for i, opt in enumerate([""] + p["options"]) if i > 0
401
+ )
402
+ send_message(mid, chat, txt)
403
+ else:
404
+ send_message(mid, chat, "No active poll.")
405
  return {"success": True}
406
 
407
+ if low.startswith("/gen"):
408
+ prompt = body[len("/gen"):].strip()
409
+ if not prompt:
410
+ send_message(mid, chat, "Please use `/gen <prompt>` to generate an image.")
411
+ else:
412
+ send_message(mid, chat, "✨ Your image is being generated. Please wait...")
413
+ task_queue.put({
414
+ "type": "image",
415
+ "message_id": mid,
416
+ "chat_id": chat,
417
+ "prompt": prompt
418
+ })
419
+ return {"success": True}
420
+
421
+ # Fallback: voice reply for any other text
422
+ task_queue.put({
423
+ "type": "audio",
424
+ "message_id": mid,
425
+ "chat_id": chat,
426
+ "prompt": body
427
+ })
428
  return {"success": True}
429
 
430
  @app.get("/", response_class=PlainTextResponse)
431
  def index():
432
+ return "Server is running!"
433
 
434
  if __name__ == "__main__":
435
+ # Send startup message on launch
436
+ def send_startup_message():
437
+ if BOT_GROUP_CHAT:
438
+ startup_msg = "🌟 Hi! I'm Eve, your friendly AI assistant. I'm now live and ready to help with images, voice replies, and more!"
439
+ send_message_to_chat(BOT_GROUP_CHAT, startup_msg)
440
+ else:
441
+ logging.warning("BOT_GROUP_CHAT is not set; startup message not sent.")
442
  send_startup_message()
443
  import uvicorn
444
  uvicorn.run(app, host="0.0.0.0", port=7860)