ragV98 commited on
Commit
bbce544
·
1 Parent(s): 6e64b9c

message handling changes

Browse files
Files changed (1) hide show
  1. routes/api/whatsapp_webhook.py +72 -29
routes/api/whatsapp_webhook.py CHANGED
@@ -11,6 +11,67 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
11
 
12
  router = APIRouter()
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  # WhatsApp/Gupshup webhook endpoint
15
  @router.post("/message-received")
16
  async def whatsapp_webhook_receiver(request: Request):
@@ -30,40 +91,22 @@ async def whatsapp_webhook_receiver(request: Request):
30
  logging.error("❌ Failed to decode webhook body as JSON")
31
  return JSONResponse(status_code=400, content={"error": "Invalid JSON format"})
32
 
33
-
34
- from_number = None
35
- message_text = None
36
-
37
- # <<< FIX 2: Robustly extract data from the nested structure >>>
38
- entries = incoming_message.get('entry', [])
39
- for entry in entries:
40
- changes = entry.get('changes', [])
41
- for change in changes:
42
- # We are interested in changes related to 'messages'
43
- if change.get('field') == 'messages':
44
- value = change.get('value', {})
45
- messages_list = value.get('messages', [])
46
-
47
- for msg in messages_list:
48
- # Check if it's a text message and extract sender/body
49
- if msg.get('type') == 'text':
50
- from_number = msg.get('from')
51
- message_text = msg.get('text', {}).get('body')
52
- break # Found text message, exit inner loop
53
- if from_number and message_text:
54
- break # Found message, exit middle loop
55
- if from_number and message_text:
56
- break # Found message, exit outer loop
57
 
58
  # Check if sender and message text were successfully extracted
59
  if not from_number or not message_text:
60
- logging.warning("Received webhook without valid sender or message text in expected structure.")
61
- return JSONResponse(status_code=200, content={"status": "ignored", "message": "Missing sender or text"})
 
62
 
63
  logging.info(f"Message from {from_number}: {message_text}")
64
 
 
 
 
65
  # Check for specific commands to send the digest
66
- if message_text.lower().strip() == "view today's headlines":
67
  logging.info(f"User {from_number} requested daily digest.")
68
 
69
  # Fetch the digest headlines
@@ -106,7 +149,7 @@ async def whatsapp_webhook_verify(request: Request):
106
  if mode == "subscribe" and challenge:
107
  logging.info(f"Webhook verification successful. Challenge: {challenge}")
108
  # Challenge needs to be returned as an integer, not string
109
- return JSONResponse(status_code=200, content=int(challenge))
110
  else:
111
  logging.warning(f"Webhook verification failed. Mode: {mode}, Challenge: {challenge}")
112
- raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Verification failed")
 
11
 
12
  router = APIRouter()
13
 
14
+ def _extract_from_number_and_text(payload: dict):
15
+ """
16
+ Extracts (from_number, message_text) from a WhatsApp webhook payload.
17
+ Supports text, button, and interactive replies. Silently ignores non-message webhooks.
18
+ Returns (None, None) if not a user message.
19
+ """
20
+ try:
21
+ entries = payload.get("entry", [])
22
+ for entry in entries:
23
+ changes = entry.get("changes", [])
24
+ for change in changes:
25
+ # Only process message-type changes
26
+ if change.get("field") != "messages":
27
+ continue
28
+
29
+ value = change.get("value", {})
30
+ messages_list = value.get("messages", [])
31
+ if not messages_list:
32
+ # This may be a status/billing event without 'messages'
33
+ continue
34
+
35
+ # We only look at the first message in the list for this webhook
36
+ msg = messages_list[0]
37
+ from_number = msg.get("from")
38
+ mtype = msg.get("type")
39
+
40
+ # 1) Plain text
41
+ if mtype == "text":
42
+ text_body = (msg.get("text", {}) or {}).get("body")
43
+ if from_number and text_body:
44
+ return from_number, text_body
45
+
46
+ # 2) Template reply button (older/simple schema)
47
+ # payload is the stable key if you set it; fallback to text/title
48
+ if mtype == "button":
49
+ b = msg.get("button", {}) or {}
50
+ intent = b.get("payload") or b.get("text")
51
+ if from_number and intent:
52
+ return from_number, intent
53
+
54
+ # 3) Newer interactive replies (buttons or list)
55
+ if mtype == "interactive":
56
+ i = msg.get("interactive", {}) or {}
57
+ # Prefer stable IDs over display titles
58
+ if "button_reply" in i:
59
+ intent = i["button_reply"].get("id") or i["button_reply"].get("title")
60
+ if from_number and intent:
61
+ return from_number, intent
62
+ if "list_reply" in i:
63
+ intent = i["list_reply"].get("id") or i["list_reply"].get("title")
64
+ if from_number and intent:
65
+ return from_number, intent
66
+
67
+ # If we got here, no usable user message was found
68
+ return None, None
69
+
70
+ except Exception:
71
+ # In case of any unexpected structure, just treat as no user message
72
+ return None, None
73
+
74
+
75
  # WhatsApp/Gupshup webhook endpoint
76
  @router.post("/message-received")
77
  async def whatsapp_webhook_receiver(request: Request):
 
91
  logging.error("❌ Failed to decode webhook body as JSON")
92
  return JSONResponse(status_code=400, content={"error": "Invalid JSON format"})
93
 
94
+ # <<< FIX 2: Robustly extract data from the nested structure (text/button/interactive) >>>
95
+ from_number, message_text = _extract_from_number_and_text(incoming_message)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  # Check if sender and message text were successfully extracted
98
  if not from_number or not message_text:
99
+ # This is likely a status/billing webhook or a non-user message; ignore quietly
100
+ logging.info("Ignoring non-message webhook or missing sender/text.")
101
+ return JSONResponse(status_code=200, content={"status": "ignored", "message": "No user message"})
102
 
103
  logging.info(f"Message from {from_number}: {message_text}")
104
 
105
+ # Normalize intent text lightly (don't change your core logic)
106
+ normalized = message_text.lower().strip().replace("’", "'")
107
+
108
  # Check for specific commands to send the digest
109
+ if normalized == "view today's headlines":
110
  logging.info(f"User {from_number} requested daily digest.")
111
 
112
  # Fetch the digest headlines
 
149
  if mode == "subscribe" and challenge:
150
  logging.info(f"Webhook verification successful. Challenge: {challenge}")
151
  # Challenge needs to be returned as an integer, not string
152
+ return JSONResponse(status_code=200, content=int(challenge))
153
  else:
154
  logging.warning(f"Webhook verification failed. Mode: {mode}, Challenge: {challenge}")
155
+ raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Verification failed")