Mark-Lasfar commited on
Commit
f042a56
·
1 Parent(s): 644e24c

Final: Clean bot structure with Cloudflare Worker proxy and voice support & Add Messenger bot webhook endpoint

Browse files
Files changed (3) hide show
  1. main.py +7 -0
  2. utils/messenger_bot.py +290 -0
  3. utils/whatsapp_bot.py +315 -0
main.py CHANGED
@@ -16,6 +16,10 @@ from api.endpoints import router as api_router
16
  from api.auth import fastapi_users, auth_backend, current_active_user, get_auth_router
17
  from utils.telegram_bot import router as telegram_router
18
 
 
 
 
 
19
  from api.database import User, Conversation, get_db, init_db
20
  from api.models import UserRead, UserCreate, UserUpdate
21
  from motor.motor_asyncio import AsyncIOMotorClient
@@ -171,6 +175,9 @@ logger.debug("CORS middleware configured with allowed origins")
171
  app.include_router(api_router)
172
  app.include_router(telegram_router, prefix="/api")
173
 
 
 
 
174
  get_auth_router(app)
175
  logger.debug("API and auth routers included")
176
 
 
16
  from api.auth import fastapi_users, auth_backend, current_active_user, get_auth_router
17
  from utils.telegram_bot import router as telegram_router
18
 
19
+ from utils.whatsapp_bot import router as whatsapp_router
20
+ from utils.messenger_bot import router as messenger_router
21
+
22
+
23
  from api.database import User, Conversation, get_db, init_db
24
  from api.models import UserRead, UserCreate, UserUpdate
25
  from motor.motor_asyncio import AsyncIOMotorClient
 
175
  app.include_router(api_router)
176
  app.include_router(telegram_router, prefix="/api")
177
 
178
+ app.include_router(whatsapp_router, prefix="/api")
179
+ app.include_router(messenger_router, prefix="/api")
180
+
181
  get_auth_router(app)
182
  logger.debug("API and auth routers included")
183
 
utils/messenger_bot.py CHANGED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/messenger_bot.py
2
+ # Facebook Messenger Bot Webhook Handler - Global Production Ready
3
+ # Supports ALL languages (Same as Telegram bot)
4
+
5
+ import os
6
+ import json
7
+ import logging
8
+ import httpx
9
+ from typing import Dict, Any, Optional
10
+ from fastapi import APIRouter, Request, HTTPException, Response
11
+ from utils.generation import request_generation
12
+ from api.endpoints import enhance_system_prompt
13
+ from utils.constants import API_ENDPOINT, MODEL_NAME
14
+ from utils.generation import HF_TOKEN, BACKUP_HF_TOKEN
15
+
16
+ logger = logging.getLogger(__name__)
17
+ router = APIRouter()
18
+
19
+ # ============================================================
20
+ # Configuration
21
+ # ============================================================
22
+ MESSENGER_PAGE_TOKEN = os.getenv("MESSENGER_PAGE_TOKEN")
23
+ MESSENGER_VERIFY_TOKEN = os.getenv("MESSENGER_VERIFY_TOKEN", "mgzon_messenger_verify_123")
24
+ CLOUDFLARE_WORKER_URL = os.getenv("CLOUDFLARE_WORKER_URL", "https://mgzon-tg-proxy.amarlasfar0.workers.dev")
25
+
26
+ if not MESSENGER_PAGE_TOKEN:
27
+ logger.warning("⚠️ MESSENGER_PAGE_TOKEN is not set. Messenger bot will not work.")
28
+
29
+ # ============================================================
30
+ # Language Detection Helper (Same as Telegram)
31
+ # ============================================================
32
+
33
+ def detect_language(text: str) -> str:
34
+ """Detect user's language from their message. Returns ISO 639-1 language code."""
35
+ if not text:
36
+ return "en"
37
+
38
+ if any(0x0600 <= ord(char) <= 0x06FF for char in text):
39
+ return "ar"
40
+ if any(0x4E00 <= ord(char) <= 0x9FFF for char in text):
41
+ return "zh"
42
+ if any(0x0400 <= ord(char) <= 0x04FF for char in text):
43
+ return "ru"
44
+ if any(char in "áéíóúñü¿¡" for char in text.lower()):
45
+ return "es"
46
+ if any(char in "àâçéèêëîïôûùÿæœ" for char in text.lower()):
47
+ return "fr"
48
+ if any(char in "äöüß" for char in text.lower()):
49
+ return "de"
50
+
51
+ return "en"
52
+
53
+ # ============================================================
54
+ # Multilingual Response Messages
55
+ # ============================================================
56
+
57
+ def get_welcome_message(lang: str) -> str:
58
+ """Welcome message in user's language"""
59
+ messages = {
60
+ "en": "🤖 *Welcome to MGZon AI Assistant on Messenger!*\n\nI'm your intelligent AI companion. Ask me anything! 🚀",
61
+ "ar": "🤖 *مرحباً بك في مساعد MGZon AI على ماسنجر!*\n\nأنا مساعدك الذكي. اسألني أي شيء! 🚀",
62
+ "zh": "🤖 *欢迎使用 Messenger 版 MGZon AI 助手!*\n\n我是你的智能伙伴。有什么可以帮你的?🚀",
63
+ "ru": "🤖 *Добро пожаловать в MGZon AI Assistant в Messenger!*\n\nЯ ваш интеллектуальный помощник. Спрашивайте что угодно! 🚀",
64
+ "es": "🤖 *¡Bienvenido a MGZon AI Assistant en Messenger!*\n\n¡Soy tu compañero IA. Pregúntame cualquier cosa! 🚀",
65
+ "fr": "🤖 *Bienvenue sur MGZon AI Assistant sur Messenger!*\n\nJe suis votre compagnon IA. Demandez-moi n'importe quoi! 🚀",
66
+ "de": "🤖 *Willkommen bei MGZon AI Assistant auf Messenger!*\n\nIch bin Ihr KI-Begleiter. Fragen Sie mich alles! 🚀"
67
+ }
68
+ return messages.get(lang, messages["en"])
69
+
70
+ def get_error_message(lang: str) -> str:
71
+ """Error message in user's language"""
72
+ messages = {
73
+ "en": "⚠️ An error occurred. Please try again in a few moments.",
74
+ "ar": "⚠️ حدث خطأ. يرجى المحاولة مرة أخرى بعد قليل.",
75
+ "zh": "⚠️ 发生错误。请稍后再试。",
76
+ "ru": "⚠️ Произошла ошибка. Пожалуйста, попробуйте позже.",
77
+ "es": "⚠️ Ocurrió un error. Por favor, inténtelo de nuevo en unos momentos.",
78
+ "fr": "⚠️ Une erreur s'est produite. Veuillez réessayer dans quelques instants.",
79
+ "de": "⚠️ Ein Fehler ist aufgetreten. Bitte versuchen Sie es in einigen Augenblicken noch einmal."
80
+ }
81
+ return messages.get(lang, messages["en"])
82
+
83
+ def get_empty_response_message(lang: str) -> str:
84
+ """Message when AI returns empty response"""
85
+ messages = {
86
+ "en": "🤔 I couldn't generate a proper response. Could you rephrase your question?",
87
+ "ar": "🤔 لم أتمكن من إنشاء رد مناسب. هل يمكنك إعادة صياغة سؤالك؟",
88
+ "zh": "🤔 我无法生成合适的回复。你能重新表述一下你的问题吗?",
89
+ "ru": "🤔 Я не смог сгенерировать подходящий ответ. Можете переформулировать вопрос?",
90
+ "es": "🤔 No pude generar una respuesta adecuada. ¿Podrías reformular tu pregunta?",
91
+ "fr": "🤔 Je n'ai pas pu générer une réponse appropriée. Pouvez-vous reformuler votre question?",
92
+ "de": "🤔 Ich konnte keine angemessene Antwort generieren. Könnten Sie Ihre Frage umformulieren?"
93
+ }
94
+ return messages.get(lang, messages["en"])
95
+
96
+ # ============================================================
97
+ # Messenger API Helpers (via Cloudflare Worker)
98
+ # ============================================================
99
+
100
+ async def send_messenger_message(recipient_id: str, text: str) -> bool:
101
+ """Send message to Facebook Messenger via Cloudflare Worker"""
102
+ if not MESSENGER_PAGE_TOKEN:
103
+ logger.error("Messenger page token not configured")
104
+ return False
105
+
106
+ url = f"{CLOUDFLARE_WORKER_URL}/api/v19.0/me/messages"
107
+
108
+ payload = {
109
+ "recipient": {"id": recipient_id},
110
+ "message": {"text": text[:2000]},
111
+ "messaging_type": "RESPONSE"
112
+ }
113
+
114
+ headers = {
115
+ "Authorization": f"Bearer {MESSENGER_PAGE_TOKEN}",
116
+ "Content-Type": "application/json"
117
+ }
118
+
119
+ async with httpx.AsyncClient(timeout=30.0) as client:
120
+ try:
121
+ response = await client.post(url, json=payload, headers=headers)
122
+ if response.status_code in (200, 201):
123
+ logger.info(f"✅ Messenger message sent to {recipient_id}")
124
+ return True
125
+ else:
126
+ logger.error(f"❌ Messenger send failed: {response.text}")
127
+ return False
128
+ except Exception as e:
129
+ logger.error(f"❌ Error sending Messenger: {e}")
130
+ return False
131
+
132
+ async def send_typing_action(recipient_id: str) -> None:
133
+ """Send typing indicator to Messenger"""
134
+ if not MESSENGER_PAGE_TOKEN:
135
+ return
136
+
137
+ url = f"{CLOUDFLARE_WORKER_URL}/api/v19.0/me/messages"
138
+
139
+ payload = {
140
+ "recipient": {"id": recipient_id},
141
+ "sender_action": "typing_on"
142
+ }
143
+
144
+ async with httpx.AsyncClient(timeout=10.0) as client:
145
+ try:
146
+ await client.post(url, json=payload, headers={"Authorization": f"Bearer {MESSENGER_PAGE_TOKEN}"})
147
+ except Exception as e:
148
+ logger.warning(f"Could not send typing indicator: {e}")
149
+
150
+ # ============================================================
151
+ # AI Call with Multi-Token Fallback
152
+ # ============================================================
153
+
154
+ async def call_ai_with_fallback(user_message: str, enhanced_prompt: str) -> str:
155
+ """Calls the AI with automatic fallback to backup tokens"""
156
+ available_tokens = []
157
+ if HF_TOKEN:
158
+ available_tokens.append(HF_TOKEN)
159
+ if BACKUP_HF_TOKEN:
160
+ available_tokens.append(BACKUP_HF_TOKEN)
161
+
162
+ if not available_tokens:
163
+ logger.error("No HF tokens available!")
164
+ user_lang = detect_language(user_message)
165
+ return get_error_message(user_lang)
166
+
167
+ for i, token in enumerate(available_tokens):
168
+ try:
169
+ logger.info(f"🔄 Trying token #{i+1} for Messenger...")
170
+
171
+ response_chunks = []
172
+ stream = request_generation(
173
+ api_key=token,
174
+ api_base=API_ENDPOINT,
175
+ message=user_message,
176
+ system_prompt=enhanced_prompt,
177
+ model_name=MODEL_NAME,
178
+ temperature=0.7,
179
+ max_new_tokens=2000,
180
+ deep_search=True,
181
+ input_type="text",
182
+ output_format="text"
183
+ )
184
+
185
+ for chunk in stream:
186
+ if isinstance(chunk, str) and chunk not in ["analysis", "assistantfinal"]:
187
+ response_chunks.append(chunk)
188
+
189
+ bot_reply = "".join(response_chunks).strip()
190
+ if bot_reply and len(bot_reply) >= 5:
191
+ logger.info(f"✅ Token #{i+1} succeeded for Messenger")
192
+ return bot_reply
193
+ else:
194
+ raise Exception("Empty or insufficient response")
195
+
196
+ except Exception as e:
197
+ logger.warning(f"⚠️ Token #{i+1} failed: {e}")
198
+ continue
199
+
200
+ logger.error("❌ All tokens failed for Messenger")
201
+ user_lang = detect_language(user_message)
202
+ return get_error_message(user_lang)
203
+
204
+ # ============================================================
205
+ # Webhook Endpoints
206
+ # ============================================================
207
+
208
+ @router.get("/messenger/webhook")
209
+ async def verify_messenger_webhook(request: Request):
210
+ """Verify webhook with Facebook (GET request)"""
211
+ mode = request.query_params.get("hub.mode")
212
+ token = request.query_params.get("hub.verify_token")
213
+ challenge = request.query_params.get("hub.challenge")
214
+
215
+ if mode == "subscribe" and token == MESSENGER_VERIFY_TOKEN:
216
+ logger.info("✅ Messenger webhook verified successfully!")
217
+ return Response(content=challenge, media_type="text/plain")
218
+ else:
219
+ logger.warning(f"❌ Messenger verification failed: mode={mode}, token={token}")
220
+ raise HTTPException(status_code=403, detail="Verification failed")
221
+
222
+ @router.post("/messenger/webhook")
223
+ async def messenger_webhook(request: Request):
224
+ """Receive Facebook Messenger messages and reply (Supports ALL languages)"""
225
+ try:
226
+ body = await request.json()
227
+ logger.debug(f"📩 Messenger webhook received: {json.dumps(body)[:500]}")
228
+
229
+ for entry in body.get("entry", []):
230
+ for messaging in entry.get("messaging", []):
231
+ sender_id = messaging.get("sender", {}).get("id")
232
+ message = messaging.get("message", {})
233
+ message_text = message.get("text")
234
+
235
+ if not sender_id:
236
+ continue
237
+
238
+ # Detect language from user's message
239
+ user_lang = detect_language(message_text or "")
240
+
241
+ if not message_text:
242
+ await send_messenger_message(sender_id, get_non_text_message(user_lang))
243
+ continue
244
+
245
+ logger.info(f"💬 Messenger from {sender_id} ({user_lang}): {message_text[:100]}...")
246
+
247
+ # Send typing indicator
248
+ await send_typing_action(sender_id)
249
+
250
+ # Prepare system prompt (language-aware)
251
+ enhanced_prompt = enhance_system_prompt("", message_text, user=None)
252
+
253
+ # Call AI
254
+ bot_reply = await call_ai_with_fallback(message_text, enhanced_prompt)
255
+
256
+ if not bot_reply or len(bot_reply) < 5:
257
+ bot_reply = get_empty_response_message(user_lang)
258
+
259
+ await send_messenger_message(sender_id, bot_reply)
260
+
261
+ return {"status": "ok"}
262
+
263
+ except Exception as e:
264
+ logger.error(f"❌ Messenger webhook error: {e}", exc_info=True)
265
+ return {"status": "error", "message": str(e)}
266
+
267
+ def get_non_text_message(lang: str) -> str:
268
+ """Message for non-text content"""
269
+ messages = {
270
+ "en": "📎 I can only process text messages for now. Please send me a text message.",
271
+ "ar": "📎 يمكنني معالجة الرسائل النصية فقط حالياً. يرجى إرسال رسالة نصية.",
272
+ "zh": "📎 我目前只能处理文本消息。请发送文本消息。",
273
+ "ru": "📎 Пока я могу обрабатывать только текстовые сообщения. Пожалуйста, отправьте текстовое сообщение.",
274
+ "es": "📎 Por ahora solo puedo procesar mensajes de texto. Por favor, envíame un mensaje de texto.",
275
+ "fr": "📎 Je ne peux traiter que les messages texte pour l'instant. Veuillez m'envoyer un message texte.",
276
+ "de": "📎 Ich kann derzeit nur Textnachrichten verarbeiten. Bitte senden Sie mir eine Textnachricht."
277
+ }
278
+ return messages.get(lang, messages["en"])
279
+
280
+ # ============================================================
281
+ # Health Check
282
+ # ============================================================
283
+
284
+ @router.get("/messenger/health")
285
+ async def messenger_health():
286
+ """Health check endpoint"""
287
+ return {
288
+ "status": "healthy",
289
+ "page_token_configured": bool(MESSENGER_PAGE_TOKEN)
290
+ }
utils/whatsapp_bot.py CHANGED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # utils/whatsapp_bot.py
2
+ # WhatsApp Business API Webhook Handler - Global Production Ready
3
+ # Supports ALL languages (Same as Telegram bot)
4
+
5
+ import os
6
+ import json
7
+ import re
8
+ import logging
9
+ import httpx
10
+ from typing import Dict, Any, Optional
11
+ from fastapi import APIRouter, Request, HTTPException, Response
12
+ from utils.generation import request_generation
13
+ from api.endpoints import enhance_system_prompt
14
+ from utils.constants import API_ENDPOINT, MODEL_NAME
15
+ from utils.generation import HF_TOKEN, BACKUP_HF_TOKEN
16
+
17
+ logger = logging.getLogger(__name__)
18
+ router = APIRouter()
19
+
20
+ # ============================================================
21
+ # Configuration
22
+ # ============================================================
23
+ WHATSAPP_TOKEN = os.getenv("WHATSAPP_TOKEN")
24
+ WHATSAPP_VERIFY_TOKEN = os.getenv("WHATSAPP_VERIFY_TOKEN")
25
+ WHATSAPP_PHONE_NUMBER_ID = os.getenv("WHATSAPP_PHONE_NUMBER_ID")
26
+ CLOUDFLARE_WORKER_URL = os.getenv("CLOUDFLARE_WORKER_URL", "https://mgzon-tg-proxy.amarlasfar0.workers.dev")
27
+
28
+ if not WHATSAPP_TOKEN:
29
+ logger.warning("⚠️ WHATSAPP_TOKEN is not set. Bot will not work.")
30
+ if not WHATSAPP_PHONE_NUMBER_ID:
31
+ logger.warning("⚠️ WHATSAPP_PHONE_NUMBER_ID is not set.")
32
+
33
+ # ============================================================
34
+ # Language Detection Helper (Same as Telegram)
35
+ # ============================================================
36
+
37
+ def detect_language(text: str) -> str:
38
+ """Detect user's language from their message. Returns ISO 639-1 language code."""
39
+ if not text:
40
+ return "en"
41
+
42
+ if any(0x0600 <= ord(char) <= 0x06FF for char in text):
43
+ return "ar"
44
+ if any(0x4E00 <= ord(char) <= 0x9FFF for char in text):
45
+ return "zh"
46
+ if any(0x0400 <= ord(char) <= 0x04FF for char in text):
47
+ return "ru"
48
+ if any(char in "áéíóúñü¿¡" for char in text.lower()):
49
+ return "es"
50
+ if any(char in "àâçéèêëîïôûùÿæœ" for char in text.lower()):
51
+ return "fr"
52
+ if any(char in "äöüß" for char in text.lower()):
53
+ return "de"
54
+
55
+ return "en"
56
+
57
+ # ============================================================
58
+ # Multilingual Response Messages
59
+ # ============================================================
60
+
61
+ def get_welcome_message(lang: str) -> str:
62
+ """Welcome message in user's language"""
63
+ messages = {
64
+ "en": "🤖 *Welcome to MGZon AI Assistant on WhatsApp!*\n\nI'm your intelligent AI companion. Ask me anything! 🚀",
65
+ "ar": "🤖 *مرحباً بك في مساعد MGZon AI على واتساب!*\n\nأنا مساعدك الذكي. اسألني أي شيء! 🚀",
66
+ "zh": "🤖 *欢迎使用 WhatsApp 版 MGZon AI 助手!*\n\n我是你的智能伙伴。有什么可以帮你的?🚀",
67
+ "ru": "🤖 *Добро пожаловать в MGZon AI Assistant в WhatsApp!*\n\nЯ ваш интеллектуальный помощник. Спрашивайте что угодно! 🚀",
68
+ "es": "🤖 *¡Bienvenido a MGZon AI Assistant en WhatsApp!*\n\n¡Soy tu compañero IA. Pregúntame cualquier cosa! 🚀",
69
+ "fr": "🤖 *Bienvenue sur MGZon AI Assistant sur WhatsApp!*\n\nJe suis votre compagnon IA. Demandez-moi n'importe quoi! 🚀",
70
+ "de": "🤖 *Willkommen bei MGZon AI Assistant auf WhatsApp!*\n\nIch bin Ihr KI-Begleiter. Fragen Sie mich alles! 🚀"
71
+ }
72
+ return messages.get(lang, messages["en"])
73
+
74
+ def get_error_message(lang: str) -> str:
75
+ """Error message in user's language"""
76
+ messages = {
77
+ "en": "⚠️ An error occurred. Please try again in a few moments.",
78
+ "ar": "⚠️ حدث خطأ. يرجى المحاولة مرة أخرى بعد قليل.",
79
+ "zh": "⚠️ 发生错误。请稍后再试。",
80
+ "ru": "⚠️ Произошла ошибка. Пожалуйста, попробуйте позже.",
81
+ "es": "⚠️ Ocurrió un error. Por favor, inténtelo de nuevo en unos momentos.",
82
+ "fr": "⚠️ Une erreur s'est produite. Veuillez réessayer dans quelques instants.",
83
+ "de": "⚠️ Ein Fehler ist aufgetreten. Bitte versuchen Sie es in einigen Augenblicken noch einmal."
84
+ }
85
+ return messages.get(lang, messages["en"])
86
+
87
+ def get_empty_response_message(lang: str) -> str:
88
+ """Message when AI returns empty response"""
89
+ messages = {
90
+ "en": "🤔 I couldn't generate a proper response. Could you rephrase your question?",
91
+ "ar": "🤔 لم أتمكن من إنشاء رد مناسب. هل يمكنك إعادة صياغة سؤالك؟",
92
+ "zh": "🤔 我无法生成合适的回复。你能重新表述一下你的问题吗?",
93
+ "ru": "🤔 Я не смог сгенерировать подходящий ответ. Можете переформулировать вопрос?",
94
+ "es": "🤔 No pude generar una respuesta adecuada. ¿Podrías reformular tu pregunta?",
95
+ "fr": "🤔 Je n'ai pas pu générer une réponse appropriée. Pouvez-vous reformuler votre question?",
96
+ "de": "🤔 Ich konnte keine angemessene Antwort generieren. Könnten Sie Ihre Frage umformulieren?"
97
+ }
98
+ return messages.get(lang, messages["en"])
99
+
100
+ # ============================================================
101
+ # WhatsApp API Helpers (via Cloudflare Worker)
102
+ # ============================================================
103
+
104
+ async def send_whatsapp_message(to_number: str, text: str, reply_to_id: Optional[str] = None) -> bool:
105
+ """Send message to WhatsApp via Cloudflare Worker"""
106
+ if not WHATSAPP_TOKEN or not WHATSAPP_PHONE_NUMBER_ID:
107
+ logger.error("WhatsApp credentials not configured")
108
+ return False
109
+
110
+ # Clean phone number
111
+ to_number = to_number.replace("+", "").strip()
112
+ if not to_number.startswith("20"):
113
+ to_number = f"20{to_number}"
114
+
115
+ url = f"{CLOUDFLARE_WORKER_URL}/api/v25.0/{WHATSAPP_PHONE_NUMBER_ID}/messages"
116
+
117
+ payload = {
118
+ "messaging_product": "whatsapp",
119
+ "to": to_number,
120
+ "text": {"body": text[:4096]},
121
+ "type": "text"
122
+ }
123
+ if reply_to_id:
124
+ payload["context"] = {"message_id": reply_to_id}
125
+
126
+ headers = {
127
+ "Authorization": f"Bearer {WHATSAPP_TOKEN}",
128
+ "Content-Type": "application/json"
129
+ }
130
+
131
+ async with httpx.AsyncClient(timeout=30.0) as client:
132
+ try:
133
+ response = await client.post(url, json=payload, headers=headers)
134
+ if response.status_code in (200, 201):
135
+ logger.info(f"✅ WhatsApp message sent to {to_number}")
136
+ return True
137
+ else:
138
+ logger.error(f"❌ WhatsApp send failed: {response.text}")
139
+ return False
140
+ except Exception as e:
141
+ logger.error(f"❌ Error sending WhatsApp: {e}")
142
+ return False
143
+
144
+ async def send_typing_indicator(to_number: str) -> None:
145
+ """Send typing indicator to WhatsApp"""
146
+ if not WHATSAPP_TOKEN or not WHATSAPP_PHONE_NUMBER_ID:
147
+ return
148
+
149
+ url = f"{CLOUDFLARE_WORKER_URL}/api/v25.0/{WHATSAPP_PHONE_NUMBER_ID}/messages"
150
+
151
+ payload = {
152
+ "messaging_product": "whatsapp",
153
+ "to": to_number,
154
+ "type": "reaction",
155
+ "reaction": {"message_id": "typing_indicator", "emoji": "⏳"}
156
+ }
157
+
158
+ async with httpx.AsyncClient(timeout=10.0) as client:
159
+ try:
160
+ await client.post(url, json=payload, headers={"Authorization": f"Bearer {WHATSAPP_TOKEN}"})
161
+ except Exception as e:
162
+ logger.warning(f"Could not send typing indicator: {e}")
163
+
164
+ # ============================================================
165
+ # AI Call with Multi-Token Fallback
166
+ # ============================================================
167
+
168
+ async def call_ai_with_fallback(user_message: str, enhanced_prompt: str) -> str:
169
+ """Calls the AI with automatic fallback to backup tokens"""
170
+ available_tokens = []
171
+ if HF_TOKEN:
172
+ available_tokens.append(HF_TOKEN)
173
+ if BACKUP_HF_TOKEN:
174
+ available_tokens.append(BACKUP_HF_TOKEN)
175
+
176
+ if not available_tokens:
177
+ logger.error("No HF tokens available!")
178
+ user_lang = detect_language(user_message)
179
+ return get_error_message(user_lang)
180
+
181
+ for i, token in enumerate(available_tokens):
182
+ try:
183
+ logger.info(f"🔄 Trying token #{i+1} for WhatsApp...")
184
+
185
+ response_chunks = []
186
+ stream = request_generation(
187
+ api_key=token,
188
+ api_base=API_ENDPOINT,
189
+ message=user_message,
190
+ system_prompt=enhanced_prompt,
191
+ model_name=MODEL_NAME,
192
+ temperature=0.7,
193
+ max_new_tokens=2000,
194
+ deep_search=True,
195
+ input_type="text",
196
+ output_format="text"
197
+ )
198
+
199
+ for chunk in stream:
200
+ if isinstance(chunk, str) and chunk not in ["analysis", "assistantfinal"]:
201
+ response_chunks.append(chunk)
202
+
203
+ bot_reply = "".join(response_chunks).strip()
204
+ if bot_reply and len(bot_reply) >= 5:
205
+ logger.info(f"✅ Token #{i+1} succeeded for WhatsApp")
206
+ return bot_reply
207
+ else:
208
+ raise Exception("Empty or insufficient response")
209
+
210
+ except Exception as e:
211
+ logger.warning(f"⚠️ Token #{i+1} failed: {e}")
212
+ continue
213
+
214
+ logger.error("❌ All tokens failed for WhatsApp")
215
+ user_lang = detect_language(user_message)
216
+ return get_error_message(user_lang)
217
+
218
+ # ============================================================
219
+ # Webhook Endpoints
220
+ # ============================================================
221
+
222
+ @router.get("/whatsapp/webhook")
223
+ async def verify_whatsapp_webhook(request: Request):
224
+ """Verify webhook with Meta (GET request)"""
225
+ mode = request.query_params.get("hub.mode")
226
+ token = request.query_params.get("hub.verify_token")
227
+ challenge = request.query_params.get("hub.challenge")
228
+
229
+ if mode == "subscribe" and token == WHATSAPP_VERIFY_TOKEN:
230
+ logger.info("✅ WhatsApp webhook verified successfully!")
231
+ return Response(content=challenge, media_type="text/plain")
232
+ else:
233
+ logger.warning(f"❌ WhatsApp verification failed: mode={mode}, token={token}")
234
+ raise HTTPException(status_code=403, detail="Verification failed")
235
+
236
+ @router.post("/whatsapp/webhook")
237
+ async def whatsapp_webhook(request: Request):
238
+ """Receive WhatsApp messages and reply (Supports ALL languages)"""
239
+ try:
240
+ body = await request.json()
241
+ logger.debug(f"📩 WhatsApp webhook received: {json.dumps(body)[:500]}")
242
+
243
+ for entry in body.get("entry", []):
244
+ for change in entry.get("changes", []):
245
+ value = change.get("value", {})
246
+ messages = value.get("messages", [])
247
+
248
+ for message in messages:
249
+ from_number = message.get("from")
250
+ msg_type = message.get("type")
251
+ msg_id = message.get("id")
252
+
253
+ if msg_type == "text":
254
+ user_text = message.get("text", {}).get("body", "")
255
+ else:
256
+ user_text = None
257
+
258
+ if not from_number:
259
+ continue
260
+
261
+ # Detect language from user's message
262
+ user_lang = detect_language(user_text or "")
263
+
264
+ # Handle non-text messages
265
+ if not user_text:
266
+ await send_whatsapp_message(from_number, get_non_text_message(user_lang), reply_to_id=msg_id)
267
+ continue
268
+
269
+ logger.info(f"📨 WhatsApp from {from_number} ({user_lang}): {user_text[:100]}...")
270
+
271
+ # Send typing indicator
272
+ await send_typing_indicator(from_number)
273
+
274
+ # Prepare system prompt (language-aware)
275
+ enhanced_prompt = enhance_system_prompt("", user_text, user=None)
276
+
277
+ # Call AI
278
+ bot_reply = await call_ai_with_fallback(user_text, enhanced_prompt)
279
+
280
+ if not bot_reply or len(bot_reply) < 5:
281
+ bot_reply = get_empty_response_message(user_lang)
282
+
283
+ await send_whatsapp_message(from_number, bot_reply, reply_to_id=msg_id)
284
+
285
+ return {"status": "ok"}
286
+
287
+ except Exception as e:
288
+ logger.error(f"❌ WhatsApp webhook error: {e}", exc_info=True)
289
+ return {"status": "error", "message": str(e)}
290
+
291
+ def get_non_text_message(lang: str) -> str:
292
+ """Message for non-text content"""
293
+ messages = {
294
+ "en": "📎 I can only process text messages for now. Please send me a text message.",
295
+ "ar": "📎 يمكنني معالجة الرسائل النصية فقط حالياً. يرجى إرسال رسالة نصية.",
296
+ "zh": "📎 我目前只能处理文本消息。请发送文本消息。",
297
+ "ru": "📎 Пока я могу обрабатывать только текстовые сообщения. Пожалуйста, отправьте текстовое сообщение.",
298
+ "es": "📎 Por ahora solo puedo procesar mensajes de texto. Por favor, envíame un mensaje de texto.",
299
+ "fr": "📎 Je ne peux traiter que les messages texte pour l'instant. Veuillez m'envoyer un message texte.",
300
+ "de": "📎 Ich kann derzeit nur Textnachrichten verarbeiten. Bitte senden Sie mir eine Textnachricht."
301
+ }
302
+ return messages.get(lang, messages["en"])
303
+
304
+ # ============================================================
305
+ # Health Check
306
+ # ============================================================
307
+
308
+ @router.get("/whatsapp/health")
309
+ async def whatsapp_health():
310
+ """Health check endpoint"""
311
+ return {
312
+ "status": "healthy",
313
+ "token_configured": bool(WHATSAPP_TOKEN),
314
+ "phone_id_configured": bool(WHATSAPP_PHONE_NUMBER_ID)
315
+ }