Major refactor to library
This commit is contained in:
47
pkg/handlers/accounts.go
Normal file
47
pkg/handlers/accounts.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/config"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/logging"
|
||||
)
|
||||
|
||||
// Accounts returns the list of all configured WhatsApp accounts
|
||||
func (h *Handlers) Accounts(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(h.config.WhatsApp)
|
||||
}
|
||||
|
||||
// AddAccount adds a new WhatsApp account to the system
|
||||
func (h *Handlers) AddAccount(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var account config.WhatsAppConfig
|
||||
if err := json.NewDecoder(r.Body).Decode(&account); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Connect to the account
|
||||
if err := h.whatsappMgr.Connect(context.Background(), account); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Update config
|
||||
h.config.WhatsApp = append(h.config.WhatsApp, account)
|
||||
if h.configPath != "" {
|
||||
if err := config.Save(h.configPath, h.config); err != nil {
|
||||
logging.Error("Failed to save config", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
151
pkg/handlers/businessapi.go
Normal file
151
pkg/handlers/businessapi.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/logging"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/whatsapp/businessapi"
|
||||
)
|
||||
|
||||
// BusinessAPIWebhook handles both verification (GET) and webhook events (POST)
|
||||
func (h *Handlers) BusinessAPIWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
h.businessAPIWebhookVerify(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
h.businessAPIWebhookEvent(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
// businessAPIWebhookVerify handles webhook verification from Meta
|
||||
// GET /webhooks/whatsapp/{accountID}?hub.mode=subscribe&hub.verify_token=XXX&hub.challenge=YYY
|
||||
func (h *Handlers) businessAPIWebhookVerify(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 h.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)
|
||||
}
|
||||
|
||||
// businessAPIWebhookEvent handles incoming webhook events from Meta
|
||||
// POST /webhooks/whatsapp/{accountID}
|
||||
func (h *Handlers) businessAPIWebhookEvent(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 := h.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 ""
|
||||
}
|
||||
56
pkg/handlers/handlers.go
Normal file
56
pkg/handlers/handlers.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/config"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/hooks"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/whatsapp"
|
||||
)
|
||||
|
||||
// Handlers holds all HTTP handlers with their dependencies
|
||||
type Handlers struct {
|
||||
whatsappMgr *whatsapp.Manager
|
||||
hookMgr *hooks.Manager
|
||||
config *config.Config
|
||||
configPath string
|
||||
|
||||
// Auth configuration
|
||||
authConfig *AuthConfig
|
||||
}
|
||||
|
||||
// AuthConfig configures authentication behavior
|
||||
type AuthConfig struct {
|
||||
// Validator is a custom auth validator function
|
||||
// If nil, uses built-in auth (API key, basic auth)
|
||||
Validator func(r *http.Request) bool
|
||||
|
||||
// Built-in auth settings
|
||||
APIKey string
|
||||
Username string
|
||||
Password string
|
||||
|
||||
// Skip auth entirely (not recommended for production)
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
// New creates a new Handlers instance
|
||||
func New(mgr *whatsapp.Manager, hookMgr *hooks.Manager, cfg *config.Config, configPath string) *Handlers {
|
||||
return &Handlers{
|
||||
whatsappMgr: mgr,
|
||||
hookMgr: hookMgr,
|
||||
config: cfg,
|
||||
configPath: configPath,
|
||||
authConfig: &AuthConfig{
|
||||
APIKey: cfg.Server.AuthKey,
|
||||
Username: cfg.Server.Username,
|
||||
Password: cfg.Server.Password,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuthConfig sets custom auth configuration
|
||||
func (h *Handlers) WithAuthConfig(cfg *AuthConfig) *Handlers {
|
||||
h.authConfig = cfg
|
||||
return h
|
||||
}
|
||||
11
pkg/handlers/health.go
Normal file
11
pkg/handlers/health.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Health handles health check requests
|
||||
func (h *Handlers) Health(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
74
pkg/handlers/hooks.go
Normal file
74
pkg/handlers/hooks.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/config"
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/logging"
|
||||
)
|
||||
|
||||
// Hooks returns the list of all configured hooks
|
||||
func (h *Handlers) Hooks(w http.ResponseWriter, r *http.Request) {
|
||||
hooks := h.hookMgr.ListHooks()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(hooks)
|
||||
}
|
||||
|
||||
// AddHook adds a new hook to the system
|
||||
func (h *Handlers) AddHook(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var hook config.Hook
|
||||
if err := json.NewDecoder(r.Body).Decode(&hook); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
h.hookMgr.AddHook(hook)
|
||||
|
||||
// Update config
|
||||
h.config.Hooks = h.hookMgr.ListHooks()
|
||||
if h.configPath != "" {
|
||||
if err := config.Save(h.configPath, h.config); err != nil {
|
||||
logging.Error("Failed to save config", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// RemoveHook removes a hook from the system
|
||||
func (h *Handlers) RemoveHook(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.hookMgr.RemoveHook(req.ID); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Update config
|
||||
h.config.Hooks = h.hookMgr.ListHooks()
|
||||
if h.configPath != "" {
|
||||
if err := config.Save(h.configPath, h.config); err != nil {
|
||||
logging.Error("Failed to save config", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
52
pkg/handlers/media.go
Normal file
52
pkg/handlers/media.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// ServeMedia serves media files with path traversal protection
|
||||
func (h *Handlers) ServeMedia(w http.ResponseWriter, r *http.Request) {
|
||||
// Expected path format: /api/media/{accountID}/{filename}
|
||||
path := r.URL.Path[len("/api/media/"):]
|
||||
|
||||
// Split path into accountID and filename
|
||||
var accountID, filename string
|
||||
for i, ch := range path {
|
||||
if ch == '/' {
|
||||
accountID = path[:i]
|
||||
filename = path[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if accountID == "" || filename == "" {
|
||||
http.Error(w, "Invalid media path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Construct full file path
|
||||
filePath := filepath.Join(h.config.Media.DataPath, accountID, filename)
|
||||
|
||||
// Security check: ensure the resolved path is within the media directory
|
||||
mediaDir := filepath.Join(h.config.Media.DataPath, accountID)
|
||||
absFilePath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
absMediaDir, err := filepath.Abs(mediaDir)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid media directory", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if file path is within media directory (prevent directory traversal)
|
||||
if len(absFilePath) < len(absMediaDir) || absFilePath[:len(absMediaDir)] != absMediaDir {
|
||||
http.Error(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// Serve the file
|
||||
http.ServeFile(w, r, absFilePath)
|
||||
}
|
||||
71
pkg/handlers/middleware.go
Normal file
71
pkg/handlers/middleware.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package handlers
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Auth is the middleware that wraps handlers with authentication
|
||||
func (h *Handlers) Auth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// If auth is disabled
|
||||
if h.authConfig.Disabled {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// If custom validator is provided
|
||||
if h.authConfig.Validator != nil {
|
||||
if h.authConfig.Validator(r) {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Built-in auth logic (API key, basic auth)
|
||||
if h.validateBuiltinAuth(r) {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="WhatsHooked"`)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
// validateBuiltinAuth checks API key or basic auth
|
||||
func (h *Handlers) validateBuiltinAuth(r *http.Request) bool {
|
||||
// Check if any authentication is configured
|
||||
hasAuth := h.authConfig.APIKey != "" || h.authConfig.Username != "" || h.authConfig.Password != ""
|
||||
if !hasAuth {
|
||||
// No authentication configured, allow access
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for API key authentication (x-api-key header or Authorization bearer token)
|
||||
if h.authConfig.APIKey != "" {
|
||||
// Check x-api-key header
|
||||
apiKey := r.Header.Get("x-api-key")
|
||||
if apiKey == h.authConfig.APIKey {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check Authorization header for bearer token
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if len(authHeader) > 7 && authHeader[:7] == "Bearer " {
|
||||
token := authHeader[7:]
|
||||
if token == h.authConfig.APIKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for username/password authentication (HTTP Basic Auth)
|
||||
if h.authConfig.Username != "" && h.authConfig.Password != "" {
|
||||
username, password, ok := r.BasicAuth()
|
||||
if ok && username == h.authConfig.Username && password == h.authConfig.Password {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
189
pkg/handlers/send.go
Normal file
189
pkg/handlers/send.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.warky.dev/wdevs/whatshooked/pkg/utils"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
)
|
||||
|
||||
// SendMessage sends a text message via WhatsApp
|
||||
func (h *Handlers) SendMessage(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Format phone number to JID format
|
||||
formattedJID := utils.FormatPhoneToJID(req.To, h.config.Server.DefaultCountryCode)
|
||||
|
||||
jid, err := types.ParseJID(formattedJID)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid JID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.whatsappMgr.SendTextMessage(r.Context(), req.AccountID, jid, req.Text); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// SendImage sends an image via WhatsApp
|
||||
func (h *Handlers) SendImage(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to"`
|
||||
Caption string `json:"caption"`
|
||||
MimeType string `json:"mime_type"`
|
||||
ImageData string `json:"image_data"` // base64 encoded
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Decode base64 image data
|
||||
imageData, err := base64.StdEncoding.DecodeString(req.ImageData)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid base64 image data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Format phone number to JID format
|
||||
formattedJID := utils.FormatPhoneToJID(req.To, h.config.Server.DefaultCountryCode)
|
||||
jid, err := types.ParseJID(formattedJID)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid JID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Default mime type if not provided
|
||||
if req.MimeType == "" {
|
||||
req.MimeType = "image/jpeg"
|
||||
}
|
||||
|
||||
if err := h.whatsappMgr.SendImage(r.Context(), req.AccountID, jid, imageData, req.MimeType, req.Caption); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// SendVideo sends a video via WhatsApp
|
||||
func (h *Handlers) SendVideo(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to"`
|
||||
Caption string `json:"caption"`
|
||||
MimeType string `json:"mime_type"`
|
||||
VideoData string `json:"video_data"` // base64 encoded
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Decode base64 video data
|
||||
videoData, err := base64.StdEncoding.DecodeString(req.VideoData)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid base64 video data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Format phone number to JID format
|
||||
formattedJID := utils.FormatPhoneToJID(req.To, h.config.Server.DefaultCountryCode)
|
||||
jid, err := types.ParseJID(formattedJID)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid JID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Default mime type if not provided
|
||||
if req.MimeType == "" {
|
||||
req.MimeType = "video/mp4"
|
||||
}
|
||||
|
||||
if err := h.whatsappMgr.SendVideo(r.Context(), req.AccountID, jid, videoData, req.MimeType, req.Caption); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// SendDocument sends a document via WhatsApp
|
||||
func (h *Handlers) SendDocument(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AccountID string `json:"account_id"`
|
||||
To string `json:"to"`
|
||||
Caption string `json:"caption"`
|
||||
MimeType string `json:"mime_type"`
|
||||
Filename string `json:"filename"`
|
||||
DocumentData string `json:"document_data"` // base64 encoded
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Decode base64 document data
|
||||
documentData, err := base64.StdEncoding.DecodeString(req.DocumentData)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid base64 document data", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Format phone number to JID format
|
||||
formattedJID := utils.FormatPhoneToJID(req.To, h.config.Server.DefaultCountryCode)
|
||||
jid, err := types.ParseJID(formattedJID)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid JID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Default values if not provided
|
||||
if req.MimeType == "" {
|
||||
req.MimeType = "application/octet-stream"
|
||||
}
|
||||
if req.Filename == "" {
|
||||
req.Filename = "document"
|
||||
}
|
||||
|
||||
if err := h.whatsappMgr.SendDocument(r.Context(), req.AccountID, jid, documentData, req.MimeType, req.Filename, req.Caption); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
Reference in New Issue
Block a user