4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
82 lines
2.8 KiB
Python
82 lines
2.8 KiB
Python
"""WhatsApp delivery helper — posts investigation findings via Twilio."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from platform.common.truncation import truncate
|
|
from platform.notifications.delivery_errors import extract_http_error
|
|
from platform.notifications.delivery_transport import post_form
|
|
from platform.notifications.limits import MAX_MESSAGE_SIZE
|
|
from platform.notifications.redaction import redact_token
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_MESSAGE_LIMIT = MAX_MESSAGE_SIZE
|
|
_TWILIO_BASE_URL = "https://api.twilio.com/2010-04-01/Accounts"
|
|
|
|
|
|
def post_whatsapp_message_twilio(
|
|
to: str,
|
|
text: str,
|
|
account_sid: str,
|
|
auth_token: str,
|
|
from_number: str,
|
|
) -> tuple[bool, str, str]:
|
|
"""Send a WhatsApp message via Twilio Messaging API.
|
|
|
|
Returns (success, error, message_id).
|
|
"""
|
|
logger.debug("[whatsapp] post twilio message to %s", to)
|
|
url = f"{_TWILIO_BASE_URL}/{account_sid}/Messages.json"
|
|
twilio_to = to if to.startswith("whatsapp:") else f"whatsapp:{to}"
|
|
twilio_from = from_number if from_number.startswith("whatsapp:") else f"whatsapp:{from_number}"
|
|
payload = {
|
|
"From": twilio_from,
|
|
"To": twilio_to,
|
|
"Body": text,
|
|
}
|
|
response = post_form(
|
|
url,
|
|
payload,
|
|
auth=(account_sid, auth_token),
|
|
timeout=15.0,
|
|
)
|
|
if not response.ok:
|
|
error = redact_token(response.error, auth_token)
|
|
logger.warning("[whatsapp] twilio post exception: %s", error)
|
|
return False, error, ""
|
|
|
|
if response.status_code not in (200, 201):
|
|
error_message = extract_http_error(response.data, response.status_code, response.text)
|
|
error_message = redact_token(error_message, auth_token)
|
|
logger.warning("[whatsapp] twilio post failed: %s", error_message)
|
|
return False, error_message, ""
|
|
|
|
message_id = str(response.data.get("sid") or "")
|
|
return True, "", message_id
|
|
|
|
|
|
def send_whatsapp_report(
|
|
report: str,
|
|
whatsapp_ctx: dict[str, Any],
|
|
) -> tuple[bool, str]:
|
|
"""Send a truncated report to WhatsApp. Returns (success, error)."""
|
|
account_sid: str = str(whatsapp_ctx.get("account_sid") or "")
|
|
auth_token: str = str(whatsapp_ctx.get("auth_token") or "")
|
|
from_number: str = str(whatsapp_ctx.get("from_number") or "")
|
|
to: str = str(whatsapp_ctx.get("to") or "")
|
|
if not account_sid or not auth_token or not from_number or not to:
|
|
return False, "Missing account_sid, auth_token, from_number, or to"
|
|
|
|
text = truncate(report, _MESSAGE_LIMIT, suffix="…")
|
|
post_success, error, _ = post_whatsapp_message_twilio(
|
|
to=to,
|
|
text=text,
|
|
account_sid=account_sid,
|
|
auth_token=auth_token,
|
|
from_number=from_number,
|
|
)
|
|
return (True, "") if post_success else (False, error)
|