removing meta shite
Browse files
components/gateways/headlines_to_wa.py
CHANGED
|
@@ -2,8 +2,6 @@ import datetime # Import datetime for dynamic date
|
|
| 2 |
import json
|
| 3 |
import logging
|
| 4 |
import os
|
| 5 |
-
import re
|
| 6 |
-
import time
|
| 7 |
from typing import Dict, List, Optional
|
| 8 |
|
| 9 |
import redis
|
|
@@ -21,23 +19,6 @@ def getenv_strip(name: str, default: Optional[str] = None) -> Optional[str]:
|
|
| 21 |
return v.strip() if isinstance(v, str) else v
|
| 22 |
|
| 23 |
|
| 24 |
-
def only_digits(s: Optional[str]) -> Optional[str]:
|
| 25 |
-
"""Keep only digits from a string (useful for Meta Phone Number ID)."""
|
| 26 |
-
if not s:
|
| 27 |
-
return s
|
| 28 |
-
return re.sub(r"\D+", "", s)
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
def _mask_identifier(value: Optional[str]) -> str:
|
| 32 |
-
"""Mask sensitive identifiers in logs."""
|
| 33 |
-
if not value:
|
| 34 |
-
return "<unset>"
|
| 35 |
-
trimmed = value.strip()
|
| 36 |
-
if len(trimmed) <= 6:
|
| 37 |
-
return f"***{trimmed[-3:]}"
|
| 38 |
-
return f"***{trimmed[-4:]}"
|
| 39 |
-
|
| 40 |
-
|
| 41 |
# -----------------------------------------
|
| 42 |
# 🌐 Configuration from Environment Vars
|
| 43 |
# -----------------------------------------
|
|
@@ -51,17 +32,6 @@ WHATSAPP_TO_NUMBER = getenv_strip("WHATSAPP_TO_NUMBER", "353899495777")
|
|
| 51 |
GUPSHUP_SOURCE_NUMBER = getenv_strip("GUPSHUP_SOURCE_NUMBER")
|
| 52 |
GUPSHUP_APP_NAME = getenv_strip("GUPSHUP_APP_NAME")
|
| 53 |
|
| 54 |
-
# Meta (Cloud API) configuration (used for typing + mark-as-read when provider == "meta_cloud")
|
| 55 |
-
META_GRAPH_VERSION = getenv_strip("META_GRAPH_VERSION", getenv_strip("WHATSAPP_CLOUD_GRAPH_VERSION", "v23.0"))
|
| 56 |
-
META_PHONE_NUMBER_ID = only_digits(
|
| 57 |
-
getenv_strip("WHATSAPP_CLOUD_PHONE_NUMBER_ID") or getenv_strip("META_PHONE_NUMBER_ID")
|
| 58 |
-
)
|
| 59 |
-
# IMPORTANT: Do NOT default to WHATSAPP_TOKEN (that is Gupshup).
|
| 60 |
-
META_ACCESS_TOKEN = getenv_strip("WHATSAPP_CLOUD_TOKEN") or getenv_strip("META_ACCESS_TOKEN")
|
| 61 |
-
|
| 62 |
-
# Global backoff state for typing indicator attempts
|
| 63 |
-
_META_TYPING_SUPPRESS_UNTIL: Optional[float] = None
|
| 64 |
-
|
| 65 |
|
| 66 |
# ✅ Redis connection
|
| 67 |
try:
|
|
@@ -131,66 +101,6 @@ def _base_payload(destination_number: str) -> Dict[str, object]:
|
|
| 131 |
}
|
| 132 |
|
| 133 |
|
| 134 |
-
def _missing_meta_config(destination_number: Optional[str] = None) -> Optional[Dict[str, str]]:
|
| 135 |
-
missing = []
|
| 136 |
-
if not META_PHONE_NUMBER_ID:
|
| 137 |
-
missing.append("WHATSAPP_CLOUD_PHONE_NUMBER_ID (or META_PHONE_NUMBER_ID)")
|
| 138 |
-
if not META_ACCESS_TOKEN:
|
| 139 |
-
missing.append("WHATSAPP_CLOUD_TOKEN (or META_ACCESS_TOKEN)")
|
| 140 |
-
if not destination_number:
|
| 141 |
-
missing.append("destination_number")
|
| 142 |
-
if missing:
|
| 143 |
-
msg = "❌ Missing Meta Cloud API config: " + ", ".join(missing)
|
| 144 |
-
logging.error(msg)
|
| 145 |
-
return {"status": "failed", "error": msg, "code": 500}
|
| 146 |
-
return None
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
# Optional early sanity hints in logs
|
| 150 |
-
if META_PHONE_NUMBER_ID and not META_PHONE_NUMBER_ID.isdigit():
|
| 151 |
-
logging.warning("META_PHONE_NUMBER_ID looks non-numeric after cleanup. Check your env value.")
|
| 152 |
-
if META_ACCESS_TOKEN and not META_ACCESS_TOKEN.startswith("EAA"):
|
| 153 |
-
logging.warning("META_ACCESS_TOKEN doesn't look like a typical Graph token (often starts with 'EAA').")
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
# -----------------------------
|
| 157 |
-
# HTTP helpers
|
| 158 |
-
# -----------------------------
|
| 159 |
-
def _post_to_meta_messages(payload: Dict[str, object]) -> Dict[str, object]:
|
| 160 |
-
"""POST to Meta WhatsApp Cloud `/messages` endpoint with detailed diagnostics."""
|
| 161 |
-
url = f"https://graph.facebook.com/{META_GRAPH_VERSION}/{META_PHONE_NUMBER_ID}/messages"
|
| 162 |
-
logging.debug("Meta POST URL: %s", url)
|
| 163 |
-
try:
|
| 164 |
-
response = requests.post(
|
| 165 |
-
url,
|
| 166 |
-
json=payload,
|
| 167 |
-
headers={
|
| 168 |
-
"Authorization": f"Bearer {META_ACCESS_TOKEN}",
|
| 169 |
-
"Content-Type": "application/json",
|
| 170 |
-
},
|
| 171 |
-
timeout=10,
|
| 172 |
-
)
|
| 173 |
-
text = response.text # capture before raise_for_status for logging
|
| 174 |
-
if response.status_code >= 400:
|
| 175 |
-
logging.error("Meta POST %s failed: %s | %s", url, response.status_code, text)
|
| 176 |
-
try:
|
| 177 |
-
body = response.json()
|
| 178 |
-
except ValueError:
|
| 179 |
-
body = {"raw": text}
|
| 180 |
-
return {"status": "failed", "code": response.status_code, "error": text, "details": body}
|
| 181 |
-
try:
|
| 182 |
-
return {"status": "success", "details": response.json() if text else {}}
|
| 183 |
-
except ValueError:
|
| 184 |
-
return {"status": "success", "details": {"raw": text}}
|
| 185 |
-
except requests.exceptions.RequestException as exc:
|
| 186 |
-
logging.error("Meta POST error: %s", exc)
|
| 187 |
-
return {
|
| 188 |
-
"status": "failed",
|
| 189 |
-
"error": str(exc),
|
| 190 |
-
"code": getattr(getattr(exc, "response", None), "status_code", 500),
|
| 191 |
-
}
|
| 192 |
-
|
| 193 |
-
|
| 194 |
def _post_to_gupshup(payload: Dict[str, object], action: str) -> Dict[str, object]:
|
| 195 |
try:
|
| 196 |
logging.info(
|
|
@@ -306,123 +216,19 @@ def send_to_whatsapp(message_text: str, destination_number: str) -> Dict[str, ob
|
|
| 306 |
|
| 307 |
|
| 308 |
def send_seen_receipt(destination_number: str, message_id: Optional[str]) -> Dict[str, object]:
|
| 309 |
-
"""
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
logging.info(
|
| 315 |
-
"Seen receipt intent (Meta Cloud): msg_id=%s | phone_id=%s | token_prefix=%s",
|
| 316 |
message_id,
|
| 317 |
-
_mask_identifier(META_PHONE_NUMBER_ID),
|
| 318 |
-
(META_ACCESS_TOKEN[:3] if META_ACCESS_TOKEN else "<none>"),
|
| 319 |
)
|
| 320 |
-
|
| 321 |
-
if not META_PHONE_NUMBER_ID or not META_ACCESS_TOKEN:
|
| 322 |
-
error_msg = (
|
| 323 |
-
"❌ Missing WhatsApp Cloud API credentials for mark-as-read. "
|
| 324 |
-
"Set WHATSAPP_CLOUD_PHONE_NUMBER_ID (or META_PHONE_NUMBER_ID) and "
|
| 325 |
-
"WHATSAPP_CLOUD_TOKEN (or META_ACCESS_TOKEN)."
|
| 326 |
-
)
|
| 327 |
-
logging.error(error_msg)
|
| 328 |
-
return {"status": "failed", "error": error_msg, "code": 500}
|
| 329 |
-
|
| 330 |
-
url = f"https://graph.facebook.com/{META_GRAPH_VERSION}/{META_PHONE_NUMBER_ID}/messages"
|
| 331 |
-
logging.debug("Meta mark-as-read URL: %s", url)
|
| 332 |
-
payload = {
|
| 333 |
-
"messaging_product": "whatsapp",
|
| 334 |
-
"status": "read",
|
| 335 |
-
"message_id": message_id,
|
| 336 |
-
"typing_indicator": {"type": "text"},
|
| 337 |
-
}
|
| 338 |
-
|
| 339 |
-
try:
|
| 340 |
-
response = requests.post(
|
| 341 |
-
url,
|
| 342 |
-
json=payload,
|
| 343 |
-
headers={
|
| 344 |
-
"Authorization": f"Bearer {META_ACCESS_TOKEN}",
|
| 345 |
-
"Content-Type": "application/json",
|
| 346 |
-
},
|
| 347 |
-
timeout=10,
|
| 348 |
-
)
|
| 349 |
-
text = response.text
|
| 350 |
-
if response.status_code >= 400:
|
| 351 |
-
logging.error("Meta mark-as-read failed: %s | %s", response.status_code, text)
|
| 352 |
-
try:
|
| 353 |
-
body = response.json()
|
| 354 |
-
except ValueError:
|
| 355 |
-
body = {"raw": text}
|
| 356 |
-
return {"status": "failed", "details": body, "code": response.status_code}
|
| 357 |
-
|
| 358 |
-
try:
|
| 359 |
-
body = response.json()
|
| 360 |
-
except ValueError:
|
| 361 |
-
body = {"raw": text}
|
| 362 |
-
return {"status": "success", "details": body}
|
| 363 |
-
except requests.exceptions.RequestException as exc:
|
| 364 |
-
logging.error("❌ Meta mark-as-read error: %s", exc)
|
| 365 |
-
return {
|
| 366 |
-
"status": "failed",
|
| 367 |
-
"error": str(exc),
|
| 368 |
-
"code": getattr(getattr(exc, "response", None), "status_code", 500),
|
| 369 |
-
}
|
| 370 |
|
| 371 |
|
| 372 |
def send_typing_indicator(destination_number: str, status: str = "typing") -> Dict[str, object]:
|
| 373 |
-
"""Send typing hints via Meta's WhatsApp Cloud API (per official docs)."""
|
| 374 |
-
global _META_TYPING_SUPPRESS_UNTIL
|
| 375 |
-
|
| 376 |
-
if _META_TYPING_SUPPRESS_UNTIL:
|
| 377 |
-
remaining = _META_TYPING_SUPPRESS_UNTIL - time.time()
|
| 378 |
-
if remaining > 0:
|
| 379 |
-
logging.debug(
|
| 380 |
-
"Meta typing indicator suppressed for %.1fs; skipping send for %s",
|
| 381 |
-
remaining,
|
| 382 |
-
destination_number,
|
| 383 |
-
)
|
| 384 |
-
return {"status": "skipped", "reason": "meta_typing_suppressed"}
|
| 385 |
-
_META_TYPING_SUPPRESS_UNTIL = None
|
| 386 |
-
|
| 387 |
-
normalized = (status or "typing").strip().lower()
|
| 388 |
-
if normalized in {"typing_on", "start"}:
|
| 389 |
-
normalized = "typing"
|
| 390 |
-
elif normalized in {"typing_off", "stop", "stopped"}:
|
| 391 |
-
normalized = "paused"
|
| 392 |
-
elif normalized not in {"typing", "paused"}:
|
| 393 |
-
logging.debug("Unknown typing status '%s'; defaulting to 'typing'", status)
|
| 394 |
-
normalized = "typing"
|
| 395 |
-
|
| 396 |
-
config_error = _missing_meta_config(destination_number)
|
| 397 |
-
if config_error:
|
| 398 |
-
return config_error
|
| 399 |
-
|
| 400 |
logging.debug(
|
| 401 |
-
"Typing indicator
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
_mask_identifier(WHATSAPP_TO_NUMBER),
|
| 405 |
)
|
| 406 |
-
|
| 407 |
-
payload = {
|
| 408 |
-
"messaging_product": "whatsapp",
|
| 409 |
-
"to": destination_number,
|
| 410 |
-
"type": "typing",
|
| 411 |
-
"typing": {"status": normalized},
|
| 412 |
-
}
|
| 413 |
-
|
| 414 |
-
logging.info("Sending typing indicator (%s) to %s via Meta Cloud API", normalized, destination_number)
|
| 415 |
-
result = _post_to_meta_messages(payload)
|
| 416 |
-
|
| 417 |
-
if result.get("status") != "success":
|
| 418 |
-
_META_TYPING_SUPPRESS_UNTIL = time.time() + 300 # back off for 5 minutes
|
| 419 |
-
logging.warning(
|
| 420 |
-
"Meta typing indicator failed; suppressing attempts for 5 minutes (%s)",
|
| 421 |
-
result.get("error"),
|
| 422 |
-
)
|
| 423 |
-
if not META_PHONE_NUMBER_ID and not META_ACCESS_TOKEN:
|
| 424 |
-
logging.warning(
|
| 425 |
-
"Meta typing disabled until credentials are set (WHATSAPP_CLOUD_PHONE_NUMBER_ID / META_PHONE_NUMBER_ID, WHATSAPP_CLOUD_TOKEN / META_ACCESS_TOKEN)."
|
| 426 |
-
)
|
| 427 |
-
|
| 428 |
-
return result
|
|
|
|
| 2 |
import json
|
| 3 |
import logging
|
| 4 |
import os
|
|
|
|
|
|
|
| 5 |
from typing import Dict, List, Optional
|
| 6 |
|
| 7 |
import redis
|
|
|
|
| 19 |
return v.strip() if isinstance(v, str) else v
|
| 20 |
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
# -----------------------------------------
|
| 23 |
# 🌐 Configuration from Environment Vars
|
| 24 |
# -----------------------------------------
|
|
|
|
| 32 |
GUPSHUP_SOURCE_NUMBER = getenv_strip("GUPSHUP_SOURCE_NUMBER")
|
| 33 |
GUPSHUP_APP_NAME = getenv_strip("GUPSHUP_APP_NAME")
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
# ✅ Redis connection
|
| 37 |
try:
|
|
|
|
| 101 |
}
|
| 102 |
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
def _post_to_gupshup(payload: Dict[str, object], action: str) -> Dict[str, object]:
|
| 105 |
try:
|
| 106 |
logging.info(
|
|
|
|
| 216 |
|
| 217 |
|
| 218 |
def send_seen_receipt(destination_number: str, message_id: Optional[str]) -> Dict[str, object]:
|
| 219 |
+
"""Stub for mark-as-read; Meta integration disabled."""
|
| 220 |
+
logging.debug(
|
| 221 |
+
"Seen receipt skipped for %s (Meta integration disabled). message_id=%s",
|
| 222 |
+
destination_number,
|
|
|
|
|
|
|
|
|
|
| 223 |
message_id,
|
|
|
|
|
|
|
| 224 |
)
|
| 225 |
+
return {"status": "skipped", "reason": "meta_disabled"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
|
| 228 |
def send_typing_indicator(destination_number: str, status: str = "typing") -> Dict[str, object]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
logging.debug(
|
| 230 |
+
"Typing indicator '%s' skipped for %s (Meta integration disabled).",
|
| 231 |
+
status,
|
| 232 |
+
destination_number,
|
|
|
|
| 233 |
)
|
| 234 |
+
return {"status": "skipped", "reason": "meta_disabled"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|