miftahulkhairim commited on
Commit
8f8e0c3
Β·
verified Β·
1 Parent(s): fb6aae7

Add main FastAPI application

Browse files
Files changed (1) hide show
  1. main.py +562 -0
main.py ADDED
@@ -0,0 +1,562 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WhatsApp Bot - Powered by Hugging Face πŸ’¬
3
+ A FastAPI webhook handler for WhatsApp Cloud API with AI-powered responses.
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import asyncio
9
+ import logging
10
+ from datetime import datetime, timezone
11
+ from collections import deque
12
+
13
+ import httpx
14
+ from fastapi import FastAPI, Request, Response, HTTPException
15
+ from fastapi.responses import PlainTextResponse, HTMLResponse
16
+
17
+ # ─── Logging ────────────────────────────────────────────────────────────────
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger("whatsapp-bot")
20
+
21
+ # ─── App ────────────────────────────────────────────────────────────────────
22
+ app = FastAPI(title="WhatsApp Bot", version="1.0.0")
23
+
24
+ # ─── Configuration (loaded from HF Space Secrets) ──────────────────────────
25
+ VERIFY_TOKEN = os.environ.get("WHATSAPP_VERIFY_TOKEN", "")
26
+ WHATSAPP_TOKEN = os.environ.get("WHATSAPP_TOKEN", "")
27
+ PHONE_NUMBER_ID = os.environ.get("PHONE_NUMBER_ID", "")
28
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
29
+
30
+ WHATSAPP_API_URL = f"https://graph.facebook.com/v21.0/{PHONE_NUMBER_ID}/messages"
31
+
32
+ # ─── In-memory message log (last 100 messages) ─────────────────────────────
33
+ message_log = deque(maxlen=100)
34
+ bot_stats = {"messages_received": 0, "messages_sent": 0, "errors": 0, "started_at": datetime.now(timezone.utc).isoformat()}
35
+
36
+
37
+ # ═══════════════════════════════════════════════════════════════════════════
38
+ # WEBHOOK ENDPOINTS
39
+ # ═══════════════════════════════════════════════════════════════════════════
40
+
41
+ @app.get("/webhook")
42
+ async def verify_webhook(request: Request):
43
+ """
44
+ Meta webhook verification endpoint.
45
+ When you set the webhook URL in Meta Developer Portal, it sends a GET request
46
+ with hub.mode, hub.verify_token, and hub.challenge to verify ownership.
47
+ """
48
+ params = dict(request.query_params)
49
+ mode = params.get("hub.mode")
50
+ token = params.get("hub.verify_token")
51
+ challenge = params.get("hub.challenge")
52
+
53
+ logger.info(f"Webhook verification: mode={mode}, token={'***' if token else 'none'}")
54
+
55
+ if mode == "subscribe" and token == VERIFY_TOKEN:
56
+ logger.info("βœ… Webhook verified successfully!")
57
+ return PlainTextResponse(content=challenge, status_code=200)
58
+
59
+ logger.warning("❌ Webhook verification failed")
60
+ raise HTTPException(status_code=403, detail="Verification failed")
61
+
62
+
63
+ @app.post("/webhook")
64
+ async def receive_message(request: Request):
65
+ """
66
+ Receive incoming WhatsApp messages via webhook.
67
+ CRITICAL: Always return 200 quickly - WhatsApp retries on timeout.
68
+ Heavy processing (LLM calls) runs in background.
69
+ """
70
+ try:
71
+ body = await request.json()
72
+ except Exception:
73
+ return Response(status_code=200)
74
+
75
+ # Navigate the WhatsApp Cloud API payload structure
76
+ try:
77
+ entry = body["entry"][0]
78
+ changes = entry["changes"][0]
79
+ value = changes["value"]
80
+
81
+ # Check if this is a message event (not a status update)
82
+ if "messages" not in value:
83
+ return Response(status_code=200)
84
+
85
+ message = value["messages"][0]
86
+ msg_type = message["type"]
87
+ from_number = message["from"]
88
+
89
+ # Get contact name if available
90
+ contact_name = "Unknown"
91
+ if "contacts" in value and value["contacts"]:
92
+ contact_name = value["contacts"][0].get("profile", {}).get("name", "Unknown")
93
+
94
+ if msg_type == "text":
95
+ text_body = message["text"]["body"]
96
+ else:
97
+ text_body = f"[{msg_type} message - not supported yet]"
98
+
99
+ logger.info(f"πŸ“© Message from {contact_name} ({from_number}): {text_body[:100]}")
100
+
101
+ bot_stats["messages_received"] += 1
102
+ message_log.append({
103
+ "timestamp": datetime.now(timezone.utc).isoformat(),
104
+ "from": from_number,
105
+ "name": contact_name,
106
+ "message": text_body,
107
+ "direction": "incoming"
108
+ })
109
+
110
+ # Process text messages - run LLM in background to respond quickly
111
+ if msg_type == "text":
112
+ asyncio.create_task(process_and_reply(from_number, contact_name, text_body))
113
+
114
+ except (KeyError, IndexError) as e:
115
+ logger.debug(f"Non-message event received: {e}")
116
+
117
+ return Response(status_code=200)
118
+
119
+
120
+ # ═════════════════��═════════════════════════════════════════════════════════
121
+ # MESSAGE PROCESSING
122
+ # ═══════════════════════════════════════════════════════════════════════════
123
+
124
+ async def process_and_reply(to_number: str, contact_name: str, user_message: str):
125
+ """Generate AI reply and send it back via WhatsApp."""
126
+ try:
127
+ reply = await generate_reply(user_message, contact_name)
128
+ await send_whatsapp_message(to_number, reply)
129
+
130
+ bot_stats["messages_sent"] += 1
131
+ message_log.append({
132
+ "timestamp": datetime.now(timezone.utc).isoformat(),
133
+ "from": "bot",
134
+ "name": "Bot",
135
+ "message": reply[:200],
136
+ "direction": "outgoing"
137
+ })
138
+ logger.info(f"πŸ“€ Reply sent to {to_number}: {reply[:100]}")
139
+
140
+ except Exception as e:
141
+ bot_stats["errors"] += 1
142
+ logger.error(f"❌ Error processing message: {e}")
143
+ # Send a fallback message
144
+ try:
145
+ await send_whatsapp_message(
146
+ to_number,
147
+ "Sorry, I'm having trouble processing your message right now. Please try again later! πŸ™"
148
+ )
149
+ except Exception:
150
+ logger.error("❌ Failed to send fallback message")
151
+
152
+
153
+ async def generate_reply(user_message: str, contact_name: str = "User") -> str:
154
+ """
155
+ Generate an AI response using Hugging Face Inference API.
156
+ Uses Meta Llama model for high-quality conversational responses.
157
+ """
158
+ if not HF_TOKEN:
159
+ return "⚠️ Bot is not fully configured yet. Please set up the HF_TOKEN in Space secrets."
160
+
161
+ try:
162
+ from huggingface_hub import InferenceClient
163
+
164
+ client = InferenceClient(
165
+ provider="hf-inference",
166
+ api_key=HF_TOKEN,
167
+ )
168
+
169
+ system_prompt = (
170
+ "You are a helpful, friendly WhatsApp assistant. "
171
+ "Keep responses concise and conversational (under 300 words). "
172
+ "Use emojis occasionally to be friendly. "
173
+ "If asked about your capabilities, mention you're an AI assistant "
174
+ "powered by Hugging Face."
175
+ )
176
+
177
+ messages = [
178
+ {"role": "system", "content": system_prompt},
179
+ {"role": "user", "content": user_message}
180
+ ]
181
+
182
+ response = client.chat_completion(
183
+ model="meta-llama/Llama-3.1-8B-Instruct",
184
+ messages=messages,
185
+ max_tokens=512,
186
+ temperature=0.7,
187
+ )
188
+
189
+ return response.choices[0].message.content
190
+
191
+ except Exception as e:
192
+ logger.error(f"LLM error: {e}")
193
+ return f"I received your message but had trouble generating a response. Error: {str(e)[:100]}"
194
+
195
+
196
+ async def send_whatsapp_message(to: str, text: str):
197
+ """Send a text message via WhatsApp Cloud API."""
198
+ if not WHATSAPP_TOKEN or not PHONE_NUMBER_ID:
199
+ logger.warning("WhatsApp credentials not configured - skipping send")
200
+ return
201
+
202
+ headers = {
203
+ "Authorization": f"Bearer {WHATSAPP_TOKEN}",
204
+ "Content-Type": "application/json",
205
+ }
206
+
207
+ # WhatsApp has a ~4096 char limit per message; split if needed
208
+ chunks = [text[i:i+4000] for i in range(0, len(text), 4000)]
209
+
210
+ async with httpx.AsyncClient(timeout=30.0) as client:
211
+ for chunk in chunks:
212
+ payload = {
213
+ "messaging_product": "whatsapp",
214
+ "recipient_type": "individual",
215
+ "to": to,
216
+ "type": "text",
217
+ "text": {"preview_url": False, "body": chunk},
218
+ }
219
+ resp = await client.post(WHATSAPP_API_URL, json=payload, headers=headers)
220
+ if resp.status_code != 200:
221
+ logger.error(f"WhatsApp API error: {resp.status_code} - {resp.text}")
222
+ resp.raise_for_status()
223
+
224
+
225
+ # ═══════════════════════════════════════════════════════════════════════════
226
+ # DASHBOARD UI
227
+ # ═══════════════════════════════════════════════════════════════════════════
228
+
229
+ DASHBOARD_HTML = """
230
+ <!DOCTYPE html>
231
+ <html lang="en">
232
+ <head>
233
+ <meta charset="UTF-8">
234
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
235
+ <title>WhatsApp Bot Dashboard</title>
236
+ <style>
237
+ * { margin: 0; padding: 0; box-sizing: border-box; }
238
+ body {
239
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
240
+ background: linear-gradient(135deg, #075e54 0%, #128c7e 50%, #25d366 100%);
241
+ min-height: 100vh;
242
+ color: #333;
243
+ }
244
+ .container {
245
+ max-width: 900px;
246
+ margin: 0 auto;
247
+ padding: 20px;
248
+ }
249
+ .header {
250
+ text-align: center;
251
+ color: white;
252
+ padding: 30px 0;
253
+ }
254
+ .header h1 { font-size: 2.5em; margin-bottom: 10px; }
255
+ .header p { font-size: 1.1em; opacity: 0.9; }
256
+ .card {
257
+ background: white;
258
+ border-radius: 16px;
259
+ padding: 24px;
260
+ margin-bottom: 20px;
261
+ box-shadow: 0 4px 20px rgba(0,0,0,0.15);
262
+ }
263
+ .card h2 {
264
+ color: #075e54;
265
+ margin-bottom: 16px;
266
+ font-size: 1.3em;
267
+ }
268
+ .stats-grid {
269
+ display: grid;
270
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
271
+ gap: 16px;
272
+ }
273
+ .stat-box {
274
+ background: #f0faf5;
275
+ border-radius: 12px;
276
+ padding: 20px;
277
+ text-align: center;
278
+ border: 1px solid #e0f2e9;
279
+ }
280
+ .stat-box .number {
281
+ font-size: 2em;
282
+ font-weight: bold;
283
+ color: #128c7e;
284
+ }
285
+ .stat-box .label {
286
+ color: #666;
287
+ font-size: 0.9em;
288
+ margin-top: 4px;
289
+ }
290
+ .status-badge {
291
+ display: inline-block;
292
+ padding: 6px 16px;
293
+ border-radius: 20px;
294
+ font-size: 0.9em;
295
+ font-weight: 600;
296
+ }
297
+ .status-ok { background: #d4edda; color: #155724; }
298
+ .status-warn { background: #fff3cd; color: #856404; }
299
+ .status-err { background: #f8d7da; color: #721c24; }
300
+ .config-table {
301
+ width: 100%;
302
+ border-collapse: collapse;
303
+ }
304
+ .config-table td {
305
+ padding: 10px 12px;
306
+ border-bottom: 1px solid #eee;
307
+ }
308
+ .config-table td:first-child {
309
+ font-weight: 600;
310
+ color: #555;
311
+ width: 220px;
312
+ }
313
+ .messages-list {
314
+ max-height: 400px;
315
+ overflow-y: auto;
316
+ }
317
+ .msg-item {
318
+ padding: 12px;
319
+ border-bottom: 1px solid #f0f0f0;
320
+ display: flex;
321
+ gap: 12px;
322
+ align-items: flex-start;
323
+ }
324
+ .msg-item:last-child { border-bottom: none; }
325
+ .msg-icon {
326
+ width: 36px;
327
+ height: 36px;
328
+ border-radius: 50%;
329
+ display: flex;
330
+ align-items: center;
331
+ justify-content: center;
332
+ font-size: 1.2em;
333
+ flex-shrink: 0;
334
+ }
335
+ .msg-incoming .msg-icon { background: #dcf8c6; }
336
+ .msg-outgoing .msg-icon { background: #e3f2fd; }
337
+ .msg-text { font-size: 0.95em; color: #333; }
338
+ .msg-meta { font-size: 0.8em; color: #999; margin-top: 2px; }
339
+ .empty-state {
340
+ text-align: center;
341
+ padding: 40px;
342
+ color: #999;
343
+ }
344
+ .setup-step {
345
+ padding: 12px 0;
346
+ border-bottom: 1px solid #f0f0f0;
347
+ display: flex;
348
+ gap: 12px;
349
+ }
350
+ .setup-step:last-child { border-bottom: none; }
351
+ .step-num {
352
+ width: 28px;
353
+ height: 28px;
354
+ border-radius: 50%;
355
+ background: #128c7e;
356
+ color: white;
357
+ display: flex;
358
+ align-items: center;
359
+ justify-content: center;
360
+ font-weight: bold;
361
+ font-size: 0.85em;
362
+ flex-shrink: 0;
363
+ }
364
+ code {
365
+ background: #f4f4f4;
366
+ padding: 2px 8px;
367
+ border-radius: 4px;
368
+ font-size: 0.9em;
369
+ }
370
+ .refresh-btn {
371
+ background: #128c7e;
372
+ color: white;
373
+ border: none;
374
+ padding: 8px 20px;
375
+ border-radius: 8px;
376
+ cursor: pointer;
377
+ font-size: 0.95em;
378
+ }
379
+ .refresh-btn:hover { background: #0a6b5e; }
380
+ </style>
381
+ </head>
382
+ <body>
383
+ <div class="container">
384
+ <div class="header">
385
+ <h1>πŸ’¬ WhatsApp Bot</h1>
386
+ <p>AI-Powered Assistant on Hugging Face</p>
387
+ </div>
388
+
389
+ <!-- Status Card -->
390
+ <div class="card">
391
+ <h2>🟒 Bot Status</h2>
392
+ <div class="stats-grid">
393
+ <div class="stat-box">
394
+ <div class="number">{messages_received}</div>
395
+ <div class="label">Messages Received</div>
396
+ </div>
397
+ <div class="stat-box">
398
+ <div class="number">{messages_sent}</div>
399
+ <div class="label">Replies Sent</div>
400
+ </div>
401
+ <div class="stat-box">
402
+ <div class="number">{errors}</div>
403
+ <div class="label">Errors</div>
404
+ </div>
405
+ </div>
406
+ </div>
407
+
408
+ <!-- Configuration Card -->
409
+ <div class="card">
410
+ <h2>βš™οΈ Configuration</h2>
411
+ <table class="config-table">
412
+ <tr>
413
+ <td>Webhook URL</td>
414
+ <td><code>https://miftahulkhairim-whatsapp-bot.hf.space/webhook</code></td>
415
+ </tr>
416
+ <tr>
417
+ <td>WhatsApp Verify Token</td>
418
+ <td><span class="status-badge {verify_status}">{verify_label}</span></td>
419
+ </tr>
420
+ <tr>
421
+ <td>WhatsApp Access Token</td>
422
+ <td><span class="status-badge {wa_token_status}">{wa_token_label}</span></td>
423
+ </tr>
424
+ <tr>
425
+ <td>Phone Number ID</td>
426
+ <td><span class="status-badge {phone_status}">{phone_label}</span></td>
427
+ </tr>
428
+ <tr>
429
+ <td>HF Token (AI Inference)</td>
430
+ <td><span class="status-badge {hf_token_status}">{hf_token_label}</span></td>
431
+ </tr>
432
+ <tr>
433
+ <td>AI Model</td>
434
+ <td><code>meta-llama/Llama-3.1-8B-Instruct</code></td>
435
+ </tr>
436
+ <tr>
437
+ <td>Running Since</td>
438
+ <td>{started_at}</td>
439
+ </tr>
440
+ </table>
441
+ </div>
442
+
443
+ <!-- Recent Messages Card -->
444
+ <div class="card">
445
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
446
+ <h2>πŸ“¨ Recent Messages</h2>
447
+ <button class="refresh-btn" onclick="location.reload()">πŸ”„ Refresh</button>
448
+ </div>
449
+ <div class="messages-list">
450
+ {messages_html}
451
+ </div>
452
+ </div>
453
+
454
+ <!-- Setup Guide Card -->
455
+ <div class="card">
456
+ <h2>πŸ“– Quick Setup Guide</h2>
457
+ <div class="setup-step">
458
+ <div class="step-num">1</div>
459
+ <div>Go to <a href="https://developers.facebook.com" target="_blank">Meta Developer Portal</a> β†’ Create App β†’ Add WhatsApp product</div>
460
+ </div>
461
+ <div class="setup-step">
462
+ <div class="step-num">2</div>
463
+ <div>In WhatsApp β†’ Configuration β†’ Set webhook URL to: <code>https://miftahulkhairim-whatsapp-bot.hf.space/webhook</code></div>
464
+ </div>
465
+ <div class="setup-step">
466
+ <div class="step-num">3</div>
467
+ <div>Add Space Secrets in Settings: <code>WHATSAPP_VERIFY_TOKEN</code>, <code>WHATSAPP_TOKEN</code>, <code>PHONE_NUMBER_ID</code>, <code>HF_TOKEN</code></div>
468
+ </div>
469
+ <div class="setup-step">
470
+ <div class="step-num">4</div>
471
+ <div>Subscribe to <code>messages</code> webhook field in Meta Developer Portal</div>
472
+ </div>
473
+ <div class="setup-step">
474
+ <div class="step-num">5</div>
475
+ <div>Send a WhatsApp message to your test number β€” the bot will reply with AI! πŸŽ‰</div>
476
+ </div>
477
+ </div>
478
+ </div>
479
+ </body>
480
+ </html>
481
+ """
482
+
483
+
484
+ @app.get("/", response_class=HTMLResponse)
485
+ async def dashboard():
486
+ """Render the bot dashboard with live stats."""
487
+
488
+ def secret_status(val, name):
489
+ if val:
490
+ return "status-ok", f"βœ… Configured"
491
+ return "status-warn", f"⚠️ Not set β€” add {name} in Space Secrets"
492
+
493
+ verify_status, verify_label = secret_status(VERIFY_TOKEN, "WHATSAPP_VERIFY_TOKEN")
494
+ wa_token_status, wa_token_label = secret_status(WHATSAPP_TOKEN, "WHATSAPP_TOKEN")
495
+ phone_status, phone_label = secret_status(PHONE_NUMBER_ID, "PHONE_NUMBER_ID")
496
+ hf_token_status, hf_token_label = secret_status(HF_TOKEN, "HF_TOKEN")
497
+
498
+ # Build messages HTML
499
+ if message_log:
500
+ msgs_html = ""
501
+ for msg in reversed(message_log):
502
+ direction = "msg-incoming" if msg["direction"] == "incoming" else "msg-outgoing"
503
+ icon = "πŸ“©" if msg["direction"] == "incoming" else "πŸ€–"
504
+ name = msg.get("name", "Unknown")
505
+ text = msg["message"][:300]
506
+ time_str = msg["timestamp"][:19].replace("T", " ")
507
+ msgs_html += f'''
508
+ <div class="msg-item {direction}">
509
+ <div class="msg-icon">{icon}</div>
510
+ <div>
511
+ <div class="msg-text">{text}</div>
512
+ <div class="msg-meta">{name} β€’ {time_str} UTC</div>
513
+ </div>
514
+ </div>'''
515
+ else:
516
+ msgs_html = '<div class="empty-state">No messages yet. Send a WhatsApp message to get started! πŸ“±</div>'
517
+
518
+ html = DASHBOARD_HTML.format(
519
+ messages_received=bot_stats["messages_received"],
520
+ messages_sent=bot_stats["messages_sent"],
521
+ errors=bot_stats["errors"],
522
+ verify_status=verify_status,
523
+ verify_label=verify_label,
524
+ wa_token_status=wa_token_status,
525
+ wa_token_label=wa_token_label,
526
+ phone_status=phone_status,
527
+ phone_label=phone_label,
528
+ hf_token_status=hf_token_status,
529
+ hf_token_label=hf_token_label,
530
+ started_at=bot_stats["started_at"][:19].replace("T", " ") + " UTC",
531
+ messages_html=msgs_html,
532
+ )
533
+ return HTMLResponse(content=html)
534
+
535
+
536
+ # ─── Health check API ──────────────────────────────────────────────────────
537
+ @app.get("/health")
538
+ async def health():
539
+ """Health check endpoint."""
540
+ return {
541
+ "status": "running",
542
+ "bot": "WhatsApp Bot",
543
+ "stats": bot_stats,
544
+ "config": {
545
+ "verify_token_set": bool(VERIFY_TOKEN),
546
+ "whatsapp_token_set": bool(WHATSAPP_TOKEN),
547
+ "phone_number_id_set": bool(PHONE_NUMBER_ID),
548
+ "hf_token_set": bool(HF_TOKEN),
549
+ }
550
+ }
551
+
552
+
553
+ # ─── Startup ───────────────────────────────────────────────────────────────
554
+ @app.on_event("startup")
555
+ async def startup():
556
+ logger.info("πŸš€ WhatsApp Bot starting up...")
557
+ logger.info(f" Verify Token: {'βœ… Set' if VERIFY_TOKEN else '❌ Not set'}")
558
+ logger.info(f" WhatsApp Token: {'βœ… Set' if WHATSAPP_TOKEN else '❌ Not set'}")
559
+ logger.info(f" Phone Number ID: {'βœ… Set' if PHONE_NUMBER_ID else '❌ Not set'}")
560
+ logger.info(f" HF Token: {'βœ… Set' if HF_TOKEN else '❌ Not set'}")
561
+ logger.info(f" Webhook URL: https://miftahulkhairim-whatsapp-bot.hf.space/webhook")
562
+ logger.info("βœ… Bot is ready to receive messages!")