152 lines
4.3 KiB
Go
152 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"git.warky.dev/wdevs/whatshooked/internal/logging"
|
|
"git.warky.dev/wdevs/whatshooked/internal/whatsapp/businessapi"
|
|
)
|
|
|
|
// handleBusinessAPIWebhook handles both verification (GET) and webhook events (POST)
|
|
func (s *Server) handleBusinessAPIWebhook(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodGet {
|
|
s.handleBusinessAPIWebhookVerify(w, r)
|
|
return
|
|
}
|
|
|
|
if r.Method == http.MethodPost {
|
|
s.handleBusinessAPIWebhookEvent(w, r)
|
|
return
|
|
}
|
|
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
|
|
// handleBusinessAPIWebhookVerify handles webhook verification from Meta
|
|
// GET /webhooks/whatsapp/{accountID}?hub.mode=subscribe&hub.verify_token=XXX&hub.challenge=YYY
|
|
func (s *Server) handleBusinessAPIWebhookVerify(w http.ResponseWriter, r *http.Request) {
|
|
// Extract account ID from URL path
|
|
accountID := extractAccountIDFromPath(r.URL.Path)
|
|
if accountID == "" {
|
|
http.Error(w, "Account ID required in path", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get the account configuration
|
|
var accountConfig *struct {
|
|
ID string
|
|
Type string
|
|
VerifyToken string
|
|
}
|
|
|
|
for _, cfg := range s.config.WhatsApp {
|
|
if cfg.ID == accountID && cfg.Type == "business-api" {
|
|
if cfg.BusinessAPI != nil {
|
|
accountConfig = &struct {
|
|
ID string
|
|
Type string
|
|
VerifyToken string
|
|
}{
|
|
ID: cfg.ID,
|
|
Type: cfg.Type,
|
|
VerifyToken: cfg.BusinessAPI.VerifyToken,
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if accountConfig == nil {
|
|
logging.Error("Business API account not found or not configured", "account_id", accountID)
|
|
http.Error(w, "Account not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Get query parameters
|
|
mode := r.URL.Query().Get("hub.mode")
|
|
token := r.URL.Query().Get("hub.verify_token")
|
|
challenge := r.URL.Query().Get("hub.challenge")
|
|
|
|
logging.Info("Webhook verification request",
|
|
"account_id", accountID,
|
|
"mode", mode,
|
|
"has_challenge", challenge != "")
|
|
|
|
// Verify the token matches
|
|
if mode == "subscribe" && token == accountConfig.VerifyToken {
|
|
logging.Info("Webhook verification successful", "account_id", accountID)
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(challenge))
|
|
return
|
|
}
|
|
|
|
logging.Warn("Webhook verification failed",
|
|
"account_id", accountID,
|
|
"mode", mode,
|
|
"token_match", token == accountConfig.VerifyToken)
|
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
}
|
|
|
|
// handleBusinessAPIWebhookEvent handles incoming webhook events from Meta
|
|
// POST /webhooks/whatsapp/{accountID}
|
|
func (s *Server) handleBusinessAPIWebhookEvent(w http.ResponseWriter, r *http.Request) {
|
|
// Extract account ID from URL path
|
|
accountID := extractAccountIDFromPath(r.URL.Path)
|
|
if accountID == "" {
|
|
http.Error(w, "Account ID required in path", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get the client from the manager
|
|
client, exists := s.whatsappMgr.GetClient(accountID)
|
|
if !exists {
|
|
logging.Error("Client not found for webhook", "account_id", accountID)
|
|
http.Error(w, "Account not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Verify it's a Business API client
|
|
if client.GetType() != "business-api" {
|
|
logging.Error("Account is not a Business API client", "account_id", accountID, "type", client.GetType())
|
|
http.Error(w, "Not a Business API account", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Cast to Business API client to access HandleWebhook
|
|
baClient, ok := client.(*businessapi.Client)
|
|
if !ok {
|
|
logging.Error("Failed to cast to Business API client", "account_id", accountID)
|
|
http.Error(w, "Internal error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Process the webhook
|
|
if err := baClient.HandleWebhook(r); err != nil {
|
|
logging.Error("Failed to process webhook", "account_id", accountID, "error", err)
|
|
http.Error(w, "Internal error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Return 200 OK to acknowledge receipt
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("OK"))
|
|
}
|
|
|
|
// extractAccountIDFromPath extracts the account ID from the URL path
|
|
// Example: /webhooks/whatsapp/business -> "business"
|
|
func extractAccountIDFromPath(path string) string {
|
|
// Remove trailing slash if present
|
|
path = strings.TrimSuffix(path, "/")
|
|
|
|
// Split by /
|
|
parts := strings.Split(path, "/")
|
|
|
|
// Expected format: /webhooks/whatsapp/{accountID}
|
|
if len(parts) >= 4 {
|
|
return parts[3]
|
|
}
|
|
|
|
return ""
|
|
}
|