diff --git a/chatplan.xml b/chatplan.xml index a025bb9..c6a3667 100644 --- a/chatplan.xml +++ b/chatplan.xml @@ -1,23 +1,31 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/outbound-hook.php b/outbound-hook.php index b3c4765..ea04fd9 100644 --- a/outbound-hook.php +++ b/outbound-hook.php @@ -14,31 +14,45 @@ // } $event = json_decode(file_get_contents('php://input')); -// if (!$event) { -// error_log("failed to parse request body: ".$postbody); -// http_response_code(400); -// die(); -// } +if (!$event) { + error_log("outbound-hook: failed to parse request body"); + http_response_code(400); + die(); +} -$domain_name = $event->{'from_host'}; -$from = $event->from; -$to = $event->to; -$contentType = $event->contentType; -$body = urldecode($event->body); -$dedupeID = random_bytes(16); -$message_uuid = urldecode($event->id); +// ── Shared: domain is always from_host ── +$domain_name = $event->from_host; +// ── Normalize fields based on payload source ── +// Web UI sends extensionUUID (camelCase); FS event has extension_uuid (set by chatplan user_data) +if (isset($event->extensionUUID)) { + // Web UI payload (Ian's webtexting) + $to = $event->to; + $contentType = $event->contentType; + $body = urldecode($event->body); + $dedupeID = urldecode($event->id); $extensionUUID = $event->extensionUUID; - $sql = "SELECT webtexting_destinations.phone_number, v_domains.domain_uuid FROM webtexting_destinations, v_domains, v_extensions WHERE v_domains.domain_name = :domain_name AND v_domains.domain_uuid = v_extensions.domain_uuid AND v_extensions.extension_uuid = :extensionUUID AND webtexting_destinations.extension_uuid = v_extensions.extension_uuid"; - $parameters['domain_name'] = $domain_name; - $parameters['extensionUUID'] = $extensionUUID; - $db = new database; - $destination = $db->select($sql, $parameters, 'row'); - if (!$destination) { - error_log("dropping outbound message from user with no configured destination: ".$extension."@".$domain_name); - die(); - } +} else { + // FreeSWITCH event payload (from chatplan Lua via mod_curl) + // extension_uuid set by chatplan: ${user_data(${from_user}@${from_host} var extension_uuid)} + $to = $event->to_user; + $contentType = isset($event->type) ? $event->type : 'text/plain'; + $body = isset($event->_body) ? urldecode($event->_body) : ''; + $dedupeID = isset($event->{'sip_h_X-Message-ID'}) ? $event->{'sip_h_X-Message-ID'} : uuid(); + $extensionUUID = $event->extension_uuid; +} +// ── Shared: look up phone number + domain UUID from extensionUUID (original query) ── +$sql = "SELECT webtexting_destinations.phone_number, v_domains.domain_uuid FROM webtexting_destinations, v_domains, v_extensions WHERE v_domains.domain_name = :domain_name AND v_domains.domain_uuid = v_extensions.domain_uuid AND v_extensions.extension_uuid = :extensionUUID AND webtexting_destinations.extension_uuid = v_extensions.extension_uuid"; +$parameters['domain_name'] = $domain_name; +$parameters['extensionUUID'] = $extensionUUID; +$db = new database; +$destination = $db->select($sql, $parameters, 'row'); +if (!$destination) { + error_log("outbound-hook: no configured destination for ".$extensionUUID."@".$domain_name); + http_response_code(404); + die(); +} unset($parameters); $from = $destination['phone_number']; $domainUUID = $destination['domain_uuid']; diff --git a/src/Messages.php b/src/Messages.php index 61db041..b3e5517 100644 --- a/src/Messages.php +++ b/src/Messages.php @@ -88,9 +88,8 @@ private static function _incoming(LocalNumber $destination, string $from, string Messages::_sendWebPush($destination->domainUUID, $destination->extensionUUID, $from, $to, $bodyStr, $groupUUID); } - // deliver via SSE or SIP or don't deliver - //_sendEvent("message", $bodyStr); - //Messages::_sendSIP($destination->domainName, $destination->extension, $from, $to, $bodyStr, $contentType, $messageUUID, $groupUUID); + // deliver via SIP + Messages::_sendSIP($destination->domainName, $destination->extension, $from, $to, $bodyStr, $contentType, $messageUUID, $groupUUID, null, true); } public static function OutgoingSMS(string $extensionUUID, string $domainUUID, string $from, string $to, string $body, string $messageUUID) @@ -372,49 +371,64 @@ private static function _sendWebPush(string $domainUUID, string $extensionUUID, unset($parameters); } } - - private static function _sendSIP(string $domainName, string $extension, string $from, string $to, string $body, string $contentType, ?string $messageUUID, ?string $groupUUID = null, ?string $originalTo = null) + + private static function _sendSIP(string $domainName, string $extension, string $from, string $to, string $body, string $contentType, ?string $dedupeID, ?string $groupUUID=null, ?string $originalTo=null, bool $inbound=false) { - $SIPProfiles = array("websocket"); // TODO: make this list configurable - $toAddress = $extension . "@" . $domainName; - $fromAddress = $from . "@" . $domainName; - - foreach ($SIPProfiles as $SIPProfile) { - $eventHeaders = array( - "Event-Subclass" => "SMS::SEND_MESSAGE", - "proto" => "sip", - "dest_proto" => "sip", - "from" => "sip:" . $from, - "from_user" => $from, - "from_host" => $domainName, - "from_full" => "sip:" . $fromAddress, - "sip_profile" => $SIPProfile, - "to" => $toAddress, - "to_user" => $extension, - "to_host" => $domainName, - "subject" => "SIMPLE MESSAGE", // is this required? what is it? fusionpbx's sms app does this - "type" => $contentType, - "hint" => "the hint", // is this required? what is it? fusionpbx's sms app does this - "replying" => "true", // what is this? - "DP_MATCH" => $toAddress, // what is this? - "sip_h_X-Message-ID" => $messageUUID, - "Content-Length" => strlen($body), - ); + $toAddress = $extension."@".$domainName; + $fromAddress = $from."@".$domainName; + + $baseHeaders = array( + "Event-Subclass" => "SMS::SEND_MESSAGE", + "proto" => "sip", + "from" => "sip:".$from, + "from_user" => $from, + "from_host" => $domainName, + "from_full" => "sip:".$fromAddress, + "to" => $toAddress, + "to_user" => $extension, + "to_host" => $domainName, + "subject" => "SIMPLE MESSAGE", + "type" => $contentType, + "hint" => "the hint", + "DP_MATCH" => $toAddress, + "sip_h_X-Message-ID" => $dedupeID, + "Content-Length" => strlen($body), + ); - if ($groupUUID != null) { - $eventHeaders['sip_h_X-Group-ID'] = $groupUUID; - } + if ($groupUUID != null) { + $baseHeaders['sip_h_X-Group-ID'] = $groupUUID; + } + if ($originalTo != null) { + $baseHeaders['sip_h_X-Original-To'] = $originalTo; + } - if ($originalTo != null) { - $eventHeaders['sip_h_X-Original-To'] = $originalTo; - } + $destinations = array( + array( + "dest_proto" => "sip", + "sip_profile" => "websocket", + "replying" => "true", + ), + ); + + // Only deliver to SIP device for inbound traffic (carrier → local user). + // For outbound, the carrier API handles all delivery (including returning + // the message if the destination is a local extension). + if ($inbound) { + $destinations[] = array( + "dest_proto" => "GLOBAL_SMS", + "context" => "public", + "inbound" => "true", + ); + } + + foreach ($destinations as $overrides) { + $eventHeaders = array_merge($baseHeaders, $overrides); $cmd = "sendevent CUSTOM\n"; foreach ($eventHeaders as $k => $v) { - $cmd .= "$k: " . $v . "\n"; + $cmd .= "$k: $v\n"; } - - $cmd .= "\n" . $body; + $cmd .= "\n".$body; event_socket_request_cmd($cmd); }