false, 'ERROR' => 'method_not_allowed'], 405); } $visitor = requireVisitorAuth(); $body = readJsonBody(); $content = trim($body['Content'] ?? ''); if (empty($content)) { jsonResponse(['OK' => false, 'ERROR' => 'content_required'], 400); } if (strlen($content) > 4000) { jsonResponse(['OK' => false, 'ERROR' => 'content_too_long', 'MaxLength' => 4000], 400); } // Rate limit check checkVisitorRateLimit((int)$visitor['ID']); $visitorId = (int)$visitor['ID']; $hostAddress = $visitor['HostAddress']; $dmChannelId = $visitor['DMChannelID'] ? (int)$visitor['DMChannelID'] : null; // Create DM channel if it doesn't exist yet if (!$dmChannelId) { $channelName = 'vc-dm-visitor-' . $visitorId; $displayName = 'VC DM: ' . $visitor['DisplayName']; queryTimed( "INSERT INTO Hub_Channels (Name, DisplayName, Purpose, ChannelType, CreatedBy) VALUES (?, ?, ?, 'direct', ?)", [ $channelName, $displayName, 'VC Gateway DM between visitor and host', $hostAddress, ] ); $dmChannelId = (int)lastInsertId(); // Add the host as a member queryTimed( "INSERT INTO Hub_ChannelMembers (ChannelID, AgentAddress, Role) VALUES (?, ?, 'owner')", [$dmChannelId, $hostAddress] ); // Link the DM channel to the visitor queryTimed( "UPDATE Hub_Visitors SET DMChannelID = ? WHERE ID = ?", [$dmChannelId, $visitorId] ); } // Send the message // Visitor sender address format: "visitor:{visitorId}:{displayName}" $senderAddress = 'visitor:' . $visitorId . ':' . $visitor['DisplayName']; queryTimed( "INSERT INTO Hub_Messages (ChannelID, SenderAddress, Content) VALUES (?, ?, ?)", [$dmChannelId, $senderAddress, $content] ); $messageId = (int)lastInsertId(); jsonResponse([ 'OK' => true, 'MessageID' => $messageId, 'DMChannelID' => $dmChannelId, 'CreatedAt' => toISO8601(date('Y-m-d H:i:s')), ]);