Chandima Prabhath commited on
Commit
6bc0800
Β·
1 Parent(s): bd4dd6a

Refactor app.py: enhance BotConfig validation, streamline environment variable checks, and improve image generation handling for better clarity and functionality.

Browse files
Files changed (1) hide show
  1. app.py +133 -88
app.py CHANGED
@@ -3,13 +3,12 @@ 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 concurrent.futures import ThreadPoolExecutor
11
  from fastapi import FastAPI, Request, HTTPException
12
- from fastapi.responses import PlainTextResponse, JSONResponse
13
  from FLUX import generate_image
14
  from VoiceReply import generate_voice_reply
15
  from polLLM import generate_llm
@@ -17,19 +16,26 @@ from polLLM import generate_llm
17
  # --- Configuration and Client Classes ---
18
 
19
  class BotConfig:
20
- GREEN_API_URL = os.getenv("GREEN_API_URL")
21
- GREEN_API_MEDIA_URL = os.getenv("GREEN_API_MEDIA_URL", "https://api.green-api.com")
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 = "[email protected]"
26
- IMAGE_DIR = "/tmp/images"
27
- AUDIO_DIR = "/tmp/audio"
 
28
 
29
  @classmethod
30
  def validate(cls):
31
- if not all([cls.GREEN_API_URL, cls.GREEN_API_TOKEN, cls.GREEN_API_ID_INSTANCE, cls.WEBHOOK_AUTH_TOKEN]):
32
- raise ValueError("Environment variables are not set properly")
 
 
 
 
 
 
33
 
34
  class BotClient:
35
  def __init__(self, cfg: BotConfig):
@@ -41,7 +47,12 @@ class BotClient:
41
  url = f"{self.cfg.GREEN_API_URL}/waInstance{self.cfg.GREEN_API_ID_INSTANCE}/{endpoint}/{self.cfg.GREEN_API_TOKEN}"
42
  for attempt in range(1, retries + 1):
43
  try:
44
- resp = self.session.post(url, json=payload if files is None else None, data=None if files is None else payload, files=files)
 
 
 
 
 
45
  resp.raise_for_status()
46
  return resp.json()
47
  except requests.RequestException as e:
@@ -52,11 +63,11 @@ class BotClient:
52
 
53
  def send_message(self, message_id: str, chat_id: str, text: str):
54
  payload = {"chatId": chat_id, "message": text, "quotedMessageId": message_id}
55
- return self.send(f"sendMessage", payload)
56
 
57
  def send_message_to(self, chat_id: str, text: str):
58
  payload = {"chatId": chat_id, "message": text}
59
- return self.send(f"sendMessage", payload)
60
 
61
  def send_media(self, message_id: str, chat_id: str, file_path: str, caption: str, media_type: str):
62
  endpoint = "sendFileByUpload"
@@ -70,35 +81,33 @@ class BotClient:
70
  BotConfig.validate()
71
  client = BotClient(BotConfig)
72
 
73
- # --- Queues, stores, and threading ---
74
 
75
  task_queue = queue.Queue()
76
  trivia_store = {}
77
  polls = {}
78
-
79
  last_message_time = time.time()
80
 
81
- # Inactivity monitor
82
  def inactivity_monitor():
83
  global last_message_time
84
  while True:
85
  time.sleep(60)
86
  if time.time() - last_message_time >= 300:
87
- client.send_message_to(BotConfig.BOT_GROUP_CHAT,
88
- "⏰ I haven't heard from you in a while! I'm still here if you need anything.")
 
 
89
  last_message_time = time.time()
90
 
91
  threading.Thread(target=inactivity_monitor, daemon=True).start()
92
 
93
- # Worker pool
94
  executor = ThreadPoolExecutor(max_workers=4)
95
-
96
  def worker():
97
  while True:
98
  task = task_queue.get()
99
  try:
100
  if task["type"] == "image":
101
- handle_image_generation(task["message_id"], task["chat_id"], task["prompt"])
102
  elif task["type"] == "audio":
103
  response_audio(task["message_id"], task["chat_id"], task["prompt"])
104
  except Exception as e:
@@ -109,7 +118,7 @@ def worker():
109
  for _ in range(4):
110
  threading.Thread(target=worker, daemon=True).start()
111
 
112
- # --- Core response functions ---
113
 
114
  def response_text(message_id, chat_id, prompt):
115
  try:
@@ -132,44 +141,55 @@ def response_audio(message_id, chat_id, prompt):
132
  logging.error(f"Audio error: {e}")
133
  client.send_message(message_id, chat_id, "Error generating audio. Try again later.")
134
 
135
- def handle_image_generation(message_id, chat_id, prompt):
136
- attempts = 4
137
- for attempt in range(1, attempts + 1):
138
- try:
139
- img, path, ret_prompt, url = generate_image(prompt, message_id, message_id, BotConfig.IMAGE_DIR)
140
- if img:
 
 
 
 
 
 
 
 
 
 
 
141
  formatted = "\n\n".join(f"_{p.strip()}_" for p in ret_prompt.split("\n\n") if p.strip())
142
- caption = f"✨ Image ready: {url}\n>{chr(8203)} {formatted}"
143
  client.send_media(message_id, chat_id, path, caption, media_type="image")
144
- return
145
- else:
146
- raise RuntimeError("generate_image returned no image")
147
- except Exception as e:
148
- logging.warning(f"Image gen attempt {attempt}/{attempts} failed: {e}")
149
- if attempt < attempts:
150
- time.sleep(5)
151
- else:
152
- client.send_message(message_id, chat_id, "😒 Image generation failed after several attempts.")
153
 
154
  # --- Startup ---
155
 
156
  def send_startup_message():
157
- client.send_message_to(BotConfig.BOT_GROUP_CHAT,
158
- "🌟 Hi! I'm Eve, your friendly AI assistant. I'm now live and ready to help!")
 
 
159
 
160
  send_startup_message()
161
 
162
  # --- FastAPI App & Webhook ---
163
 
164
  app = FastAPI()
165
-
166
  help_text = (
167
  "πŸ€– *Hi there, I'm Eve!* Here are the commands you can use:\n\n"
168
  "β€’ */help* – _Show this help message._\n"
169
  "β€’ */summarize <text>* – _Get a quick summary._\n"
170
  "β€’ */translate <language>|<text>* – _Translate text._\n"
171
  "β€’ */joke* – _Get a joke._\n"
172
- "β€’ */weather <location>* – _Get weather._\n"
 
173
  "β€’ */inspire* – _Get an inspirational quote._\n"
174
  "β€’ */trivia* – _Start trivia._\n"
175
  "β€’ */answer* – _Answer trivia._\n"
@@ -177,7 +197,7 @@ help_text = (
177
  "β€’ */poll Q|A|B…* – _Create a poll._\n"
178
  "β€’ */results* – _Show poll results._\n"
179
  "β€’ */endpoll* – _End the poll._\n"
180
- "β€’ */gen <prompt>* – _Generate an image._\n\n"
181
  "Any other text β†’ I'll send you a voice reply!"
182
  )
183
 
@@ -186,46 +206,49 @@ async def whatsapp_webhook(request: Request):
186
  global last_message_time
187
  last_message_time = time.time()
188
 
189
- # Auth
190
- auth = request.headers.get("Authorization", "")
191
- if auth != f"Bearer {BotConfig.WEBHOOK_AUTH_TOKEN}":
192
  raise HTTPException(403, "Unauthorized")
193
 
194
  data = await request.json()
195
  chat_id = data.get("senderData", {}).get("chatId")
196
- # Only respond in the group chat
197
- if chat_id != BotConfig.BOT_GROUP_CHAT:
198
- return {"success": True}
199
-
200
- if data.get("typeWebhook") != "incomingMessageReceived":
201
  return {"success": True}
202
 
203
  md = data.get("messageData", {})
204
  mid = data["idMessage"]
205
 
206
- # Extract text
207
- if "textMessageData" in md:
208
- body = md["textMessageData"]["textMessage"].strip()
209
- elif "extendedTextMessageData" in md:
210
- body = md["extendedTextMessageData"]["text"].strip()
211
- else:
 
 
 
 
 
 
 
 
 
212
  return {"success": True}
213
 
214
  low = body.lower()
215
 
216
- # Commands
217
  if low == "/help":
218
  client.send_message(mid, chat_id, help_text)
219
  return {"success": True}
220
 
221
  if low.startswith("/summarize "):
222
- txt = body[len("/summarize "):].strip()
223
- summary = generate_llm(f"Summarize this text in one short paragraph:\n\n{txt}")
224
  client.send_message(mid, chat_id, summary)
225
  return {"success": True}
226
 
227
  if low.startswith("/translate "):
228
- part = body[len("/translate "):]
229
  if "|" not in part:
230
  client.send_message(mid, chat_id, "Please use `/translate <language>|<text>`")
231
  else:
@@ -243,37 +266,53 @@ async def whatsapp_webhook(request: Request):
243
  return {"success": True}
244
 
245
  if low.startswith("/weather "):
246
- loc = body[len("/weather "):].strip().replace(" ", "+")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  try:
248
- w = requests.get(f"http://sl.wttr.in/{loc}?format=4", timeout=5).text
249
- client.send_message(mid, chat_id, w)
 
 
 
250
  except:
251
  client.send_message(mid, chat_id, "Could not fetch weather.")
252
  return {"success": True}
253
 
254
  if low == "/inspire":
255
- quote = generate_llm("Give me a short inspirational quote.")
256
  client.send_message(mid, chat_id, f"✨ {quote}")
257
  return {"success": True}
258
 
259
- # Trivia
260
  if low == "/trivia":
261
- seed = random.randint(0, 1_000_000)
262
  raw = generate_llm(
263
- f"Generate a unique trivia Q&A in JSON based on seed {seed}:\n"
264
  '{"question":"...","answer":"..."}'
265
  )
266
  try:
267
  obj = json.loads(raw.strip().strip("```json").strip("```"))
268
  trivia_store[chat_id] = obj
269
- client.send_message(mid, chat_id,
270
- f"❓ {obj['question']}\nReply `/answer` or `/answer your guess`.")
271
  except:
272
  client.send_message(mid, chat_id, "Failed to generate trivia.")
273
  return {"success": True}
274
 
275
  if low.startswith("/answer"):
276
- resp = body[len("/answer"):].strip()
277
  if chat_id in trivia_store:
278
  qa = trivia_store.pop(chat_id)
279
  if resp:
@@ -287,22 +326,18 @@ async def whatsapp_webhook(request: Request):
287
  client.send_message(mid, chat_id, "No active trivia. `/trivia` to start.")
288
  return {"success": True}
289
 
290
- # Meme
291
  if low.startswith("/meme "):
292
- txt = body[len("/meme "):].strip()
293
  client.send_message(mid, chat_id, "🎨 Generating your meme...")
294
- task_queue.put({"type":"image","message_id":mid,"chat_id":chat_id,"prompt":f"meme: {txt}"})
295
  return {"success": True}
296
 
297
- # Polls
298
  if low.startswith("/poll "):
299
- parts = body[len("/poll "):].split("|")
300
  if len(parts) < 3:
301
  client.send_message(mid, chat_id, "Use `/poll Q|A|B`")
302
  else:
303
  q, *opts = [p.strip() for p in parts]
304
- votes = {i+1:0 for i in range(len(opts))}
305
- polls[chat_id] = {"question":q,"options":opts,"votes":votes,"voters":{}}
306
  text = f"πŸ“Š *Poll:* {q}\n" + "\n".join(f"{i+1}. {o}" for i,o in enumerate(opts))
307
  client.send_message(mid, chat_id, text)
308
  return {"success": True}
@@ -322,7 +357,9 @@ async def whatsapp_webhook(request: Request):
322
  if low == "/results":
323
  if chat_id in polls:
324
  p = polls[chat_id]
325
- text = f"πŸ“Š *Results:* {p['question']}\n" + "\n".join(f"{i}. {o}: {p['votes'][i]}" for i,o in enumerate(p["options"],1))
 
 
326
  client.send_message(mid, chat_id, text)
327
  else:
328
  client.send_message(mid, chat_id, "No active poll.")
@@ -331,23 +368,31 @@ async def whatsapp_webhook(request: Request):
331
  if low == "/endpoll":
332
  if chat_id in polls:
333
  p = polls.pop(chat_id)
334
- text = f"πŸ“Š *Final Results:* {p['question']}\n" + "\n".join(f"{i}. {o}: {p['votes'][i]}" for i,o in enumerate(p["options"],1))
 
 
335
  client.send_message(mid, chat_id, text)
336
  else:
337
  client.send_message(mid, chat_id, "No active poll.")
338
  return {"success": True}
339
 
340
- # Image gen
341
  if low.startswith("/gen"):
342
- prompt = body[len("/gen"):].strip()
 
 
 
 
343
  if not prompt:
344
- client.send_message(mid, chat_id, "Use `/gen <prompt>`")
345
  else:
346
- client.send_message(mid, chat_id, "✨ Generating image...")
347
- task_queue.put({"type":"image","message_id":mid,"chat_id":chat_id,"prompt":prompt})
 
 
 
348
  return {"success": True}
349
 
350
- # Fallback: voice reply
351
  task_queue.put({"type":"audio","message_id":mid,"chat_id":chat_id,"prompt":body})
352
  return {"success": True}
353
 
 
3
  import requests
4
  import logging
5
  import queue
 
6
  import json
7
  import time
8
  import random
9
  from concurrent.futures import ThreadPoolExecutor
10
  from fastapi import FastAPI, Request, HTTPException
11
+ from fastapi.responses import PlainTextResponse
12
  from FLUX import generate_image
13
  from VoiceReply import generate_voice_reply
14
  from polLLM import generate_llm
 
16
  # --- Configuration and Client Classes ---
17
 
18
  class BotConfig:
19
+ GREEN_API_URL = os.getenv("GREEN_API_URL")
20
+ GREEN_API_MEDIA_URL = os.getenv("GREEN_API_MEDIA_URL", "https://api.green-api.com")
21
+ GREEN_API_TOKEN = os.getenv("GREEN_API_TOKEN")
22
+ GREEN_API_ID_INSTANCE = os.getenv("GREEN_API_ID_INSTANCE")
23
+ WEBHOOK_AUTH_TOKEN = os.getenv("WEBHOOK_AUTH_TOKEN")
24
+ BOT_GROUP_CHAT = "[email protected]"
25
+ IMAGE_DIR = "/tmp/images"
26
+ AUDIO_DIR = "/tmp/audio"
27
+ DEFAULT_IMAGE_COUNT = 4
28
 
29
  @classmethod
30
  def validate(cls):
31
+ missing = [name for name in (
32
+ "GREEN_API_URL",
33
+ "GREEN_API_TOKEN",
34
+ "GREEN_API_ID_INSTANCE",
35
+ "WEBHOOK_AUTH_TOKEN",
36
+ ) if not getattr(cls, name)]
37
+ if missing:
38
+ raise ValueError(f"Environment variables not set: {', '.join(missing)}")
39
 
40
  class BotClient:
41
  def __init__(self, cfg: BotConfig):
 
47
  url = f"{self.cfg.GREEN_API_URL}/waInstance{self.cfg.GREEN_API_ID_INSTANCE}/{endpoint}/{self.cfg.GREEN_API_TOKEN}"
48
  for attempt in range(1, retries + 1):
49
  try:
50
+ resp = self.session.post(
51
+ url,
52
+ json=payload if files is None else None,
53
+ data=None if files is None else payload,
54
+ files=files
55
+ )
56
  resp.raise_for_status()
57
  return resp.json()
58
  except requests.RequestException as e:
 
63
 
64
  def send_message(self, message_id: str, chat_id: str, text: str):
65
  payload = {"chatId": chat_id, "message": text, "quotedMessageId": message_id}
66
+ return self.send("sendMessage", payload)
67
 
68
  def send_message_to(self, chat_id: str, text: str):
69
  payload = {"chatId": chat_id, "message": text}
70
+ return self.send("sendMessage", payload)
71
 
72
  def send_media(self, message_id: str, chat_id: str, file_path: str, caption: str, media_type: str):
73
  endpoint = "sendFileByUpload"
 
81
  BotConfig.validate()
82
  client = BotClient(BotConfig)
83
 
84
+ # --- Queues, stores, threading ---
85
 
86
  task_queue = queue.Queue()
87
  trivia_store = {}
88
  polls = {}
 
89
  last_message_time = time.time()
90
 
 
91
  def inactivity_monitor():
92
  global last_message_time
93
  while True:
94
  time.sleep(60)
95
  if time.time() - last_message_time >= 300:
96
+ client.send_message_to(
97
+ BotConfig.BOT_GROUP_CHAT,
98
+ "⏰ I haven't heard from you in a while! I'm still here if you need anything."
99
+ )
100
  last_message_time = time.time()
101
 
102
  threading.Thread(target=inactivity_monitor, daemon=True).start()
103
 
 
104
  executor = ThreadPoolExecutor(max_workers=4)
 
105
  def worker():
106
  while True:
107
  task = task_queue.get()
108
  try:
109
  if task["type"] == "image":
110
+ handle_image_generation(task)
111
  elif task["type"] == "audio":
112
  response_audio(task["message_id"], task["chat_id"], task["prompt"])
113
  except Exception as e:
 
118
  for _ in range(4):
119
  threading.Thread(target=worker, daemon=True).start()
120
 
121
+ # --- Core responders ---
122
 
123
  def response_text(message_id, chat_id, prompt):
124
  try:
 
141
  logging.error(f"Audio error: {e}")
142
  client.send_message(message_id, chat_id, "Error generating audio. Try again later.")
143
 
144
+ def handle_image_generation(task):
145
+ message_id = task["message_id"]
146
+ chat_id = task["chat_id"]
147
+ prompt = task["prompt"]
148
+ count = task.get("num_images", BotConfig.DEFAULT_IMAGE_COUNT)
149
+
150
+ for i in range(1, count + 1):
151
+ for attempt in range(1, 4):
152
+ try:
153
+ img, path, ret_prompt, url = generate_image(
154
+ prompt,
155
+ message_id,
156
+ f"{message_id}_{i}",
157
+ BotConfig.IMAGE_DIR
158
+ )
159
+ if not img:
160
+ raise RuntimeError("No image returned")
161
  formatted = "\n\n".join(f"_{p.strip()}_" for p in ret_prompt.split("\n\n") if p.strip())
162
+ caption = f"✨ Image {i}/{count}: {url}\n>{chr(8203)} {formatted}"
163
  client.send_media(message_id, chat_id, path, caption, media_type="image")
164
+ os.remove(path)
165
+ break
166
+ except Exception as e:
167
+ logging.warning(f"Image {i}/{count} attempt {attempt} failed: {e}")
168
+ time.sleep(2 * attempt)
169
+ else:
170
+ client.send_message(message_id, chat_id, f"😒 Failed to generate image {i}.")
 
 
171
 
172
  # --- Startup ---
173
 
174
  def send_startup_message():
175
+ client.send_message_to(
176
+ BotConfig.BOT_GROUP_CHAT,
177
+ "🌟 Hi! I'm Eve, your friendly AI assistant. I'm now live and ready to help!"
178
+ )
179
 
180
  send_startup_message()
181
 
182
  # --- FastAPI App & Webhook ---
183
 
184
  app = FastAPI()
 
185
  help_text = (
186
  "πŸ€– *Hi there, I'm Eve!* Here are the commands you can use:\n\n"
187
  "β€’ */help* – _Show this help message._\n"
188
  "β€’ */summarize <text>* – _Get a quick summary._\n"
189
  "β€’ */translate <language>|<text>* – _Translate text._\n"
190
  "β€’ */joke* – _Get a joke._\n"
191
+ "β€’ */weather <location>* – _Short, creative weather report (Β°C)._ \n"
192
+ "β€’ */weatherpoem <location>* – _Poetic weather summary._\n"
193
  "β€’ */inspire* – _Get an inspirational quote._\n"
194
  "β€’ */trivia* – _Start trivia._\n"
195
  "β€’ */answer* – _Answer trivia._\n"
 
197
  "β€’ */poll Q|A|B…* – _Create a poll._\n"
198
  "β€’ */results* – _Show poll results._\n"
199
  "β€’ */endpoll* – _End the poll._\n"
200
+ "β€’ */gen <prompt>|<count>* – _Generate images (default 4)._ \n\n"
201
  "Any other text β†’ I'll send you a voice reply!"
202
  )
203
 
 
206
  global last_message_time
207
  last_message_time = time.time()
208
 
209
+ # --- Auth & basic routing ---
210
+ if request.headers.get("Authorization") != f"Bearer {BotConfig.WEBHOOK_AUTH_TOKEN}":
 
211
  raise HTTPException(403, "Unauthorized")
212
 
213
  data = await request.json()
214
  chat_id = data.get("senderData", {}).get("chatId")
215
+ if chat_id != BotConfig.BOT_GROUP_CHAT or data.get("typeWebhook") != "incomingMessageReceived":
 
 
 
 
216
  return {"success": True}
217
 
218
  md = data.get("messageData", {})
219
  mid = data["idMessage"]
220
 
221
+ # --- Extract text + contextInfo in one go ---
222
+ text_data = md.get("textMessageData") or md.get("extendedTextMessageData")
223
+ if not text_data:
224
+ return {"success": True}
225
+ body = text_data.get("textMessage", text_data.get("text", "")).strip()
226
+ ctx = text_data.get("contextInfo", {})
227
+
228
+ # --- SKIP if this is a quoted/replied message ---
229
+ if ctx.get("quotedMessageId") or ctx.get("quotedMessage"):
230
+ logging.debug("Skipping quoted/reply message")
231
+ return {"success": True}
232
+
233
+ # --- SKIP if someone is @-mentioned ---
234
+ if ctx.get("mentionedJidList"):
235
+ logging.debug("Skipping mention")
236
  return {"success": True}
237
 
238
  low = body.lower()
239
 
240
+ # --- COMMANDS ---
241
  if low == "/help":
242
  client.send_message(mid, chat_id, help_text)
243
  return {"success": True}
244
 
245
  if low.startswith("/summarize "):
246
+ summary = generate_llm(f"Summarize this text in one short paragraph:\n\n{body[11:].strip()}")
 
247
  client.send_message(mid, chat_id, summary)
248
  return {"success": True}
249
 
250
  if low.startswith("/translate "):
251
+ part = body[11:]
252
  if "|" not in part:
253
  client.send_message(mid, chat_id, "Please use `/translate <language>|<text>`")
254
  else:
 
266
  return {"success": True}
267
 
268
  if low.startswith("/weather "):
269
+ loc = body[9:].strip().replace(" ", "+")
270
+ try:
271
+ raw = requests.get(f"http://sl.wttr.in/{loc}?format=4", timeout=5).text
272
+ prompt = (
273
+ f"Convert this weather report into Celsius and craft a short, creative, "
274
+ f"beautiful weather report with emojis:\n\n{raw}"
275
+ )
276
+ report = generate_llm(prompt)
277
+ client.send_message(mid, chat_id, report)
278
+ voice_prompt = f"Provide only the following weather report as speech: {report}"
279
+ task_queue.put({"type":"audio","message_id":mid,"chat_id":chat_id,"prompt":voice_prompt})
280
+ except:
281
+ client.send_message(mid, chat_id, "Could not fetch weather.")
282
+ return {"success": True}
283
+
284
+ if low.startswith("/weatherpoem "):
285
+ loc = body[13:].strip().replace(" ", "+")
286
  try:
287
+ raw = requests.get(f"http://sl.wttr.in/{loc}?format=4", timeout=5).text
288
+ poem = generate_llm(f"Write a short, poetic weather summary in Celsius based on this:\n\n{raw}")
289
+ client.send_message(mid, chat_id, poem)
290
+ voice_prompt = f"Speak only this poetic weather summary: {poem}"
291
+ task_queue.put({"type":"audio","message_id":mid,"chat_id":chat_id,"prompt":voice_prompt})
292
  except:
293
  client.send_message(mid, chat_id, "Could not fetch weather.")
294
  return {"success": True}
295
 
296
  if low == "/inspire":
297
+ quote = generate_llm(f"Give me a short inspirational unique quote with seed {random.randint(0,1e6)}.")
298
  client.send_message(mid, chat_id, f"✨ {quote}")
299
  return {"success": True}
300
 
 
301
  if low == "/trivia":
 
302
  raw = generate_llm(
303
+ f"Generate a unique trivia Q&A in JSON:\n"
304
  '{"question":"...","answer":"..."}'
305
  )
306
  try:
307
  obj = json.loads(raw.strip().strip("```json").strip("```"))
308
  trivia_store[chat_id] = obj
309
+ client.send_message(mid, chat_id, f"❓ {obj['question']}\nReply `/answer` or `/answer your guess`.")
 
310
  except:
311
  client.send_message(mid, chat_id, "Failed to generate trivia.")
312
  return {"success": True}
313
 
314
  if low.startswith("/answer"):
315
+ resp = body[7:].strip()
316
  if chat_id in trivia_store:
317
  qa = trivia_store.pop(chat_id)
318
  if resp:
 
326
  client.send_message(mid, chat_id, "No active trivia. `/trivia` to start.")
327
  return {"success": True}
328
 
 
329
  if low.startswith("/meme "):
 
330
  client.send_message(mid, chat_id, "🎨 Generating your meme...")
331
+ task_queue.put({"type":"image","message_id":mid,"chat_id":chat_id,"prompt":f"meme: {body[6:].strip()}"})
332
  return {"success": True}
333
 
 
334
  if low.startswith("/poll "):
335
+ parts = body[6:].split("|")
336
  if len(parts) < 3:
337
  client.send_message(mid, chat_id, "Use `/poll Q|A|B`")
338
  else:
339
  q, *opts = [p.strip() for p in parts]
340
+ polls[chat_id] = {"question":q,"options":opts,"votes":{i+1:0 for i in range(len(opts))},"voters":{}}
 
341
  text = f"πŸ“Š *Poll:* {q}\n" + "\n".join(f"{i+1}. {o}" for i,o in enumerate(opts))
342
  client.send_message(mid, chat_id, text)
343
  return {"success": True}
 
357
  if low == "/results":
358
  if chat_id in polls:
359
  p = polls[chat_id]
360
+ text = f"πŸ“Š *Results:* {p['question']}\n" + "\n".join(
361
+ f"{i}. {o}: {p['votes'][i]}" for i,o in enumerate(p["options"],1)
362
+ )
363
  client.send_message(mid, chat_id, text)
364
  else:
365
  client.send_message(mid, chat_id, "No active poll.")
 
368
  if low == "/endpoll":
369
  if chat_id in polls:
370
  p = polls.pop(chat_id)
371
+ text = f"πŸ“Š *Final Results:* {p['question']}\n" + "\n".join(
372
+ f"{i}. {o}: {p['votes'][i]}" for i,o in enumerate(p["options"],1)
373
+ )
374
  client.send_message(mid, chat_id, text)
375
  else:
376
  client.send_message(mid, chat_id, "No active poll.")
377
  return {"success": True}
378
 
 
379
  if low.startswith("/gen"):
380
+ parts = body[4:].split("|", 1)
381
+ prompt = parts[0].strip()
382
+ count = BotConfig.DEFAULT_IMAGE_COUNT
383
+ if len(parts) > 1 and parts[1].strip().isdigit():
384
+ count = int(parts[1].strip())
385
  if not prompt:
386
+ client.send_message(mid, chat_id, "Use `/gen <prompt>|<count>`")
387
  else:
388
+ client.send_message(mid, chat_id, f"✨ Generating {count} image(s)...")
389
+ task_queue.put({
390
+ "type":"image","message_id":mid,"chat_id":chat_id,
391
+ "prompt":prompt,"num_images":count
392
+ })
393
  return {"success": True}
394
 
395
+ # Fallback β†’ voice reply
396
  task_queue.put({"type":"audio","message_id":mid,"chat_id":chat_id,"prompt":body})
397
  return {"success": True}
398