3c9886fa10
The three Send* helpers in helpers/whatsapp.go construct their HTTP requests directly instead of going through callAPI, and were missing the Bearer token that the bridge's JWT middleware requires. Calls reached the bridge as 401 Unauthorized. In practice sendMessageHandler in mcp_tool.go bypasses SendMessage and calls callAPI directly, so the text-send path happens to work; but the file and audio-message MCP handlers route through SendFile and SendAudioVoiceMessage respectively, so send_file and send_audio_message were broken end-to-end. Same shape as the recent DownloadMedia fix: fetch a JWT via GetOrRefreshJwtToken and add Authorization: Bearer <token> on the outgoing request.
273 lines
6.6 KiB
Go
273 lines
6.6 KiB
Go
package helpers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
type Message struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Sender string `json:"sender"`
|
|
Content string `json:"content"`
|
|
IsFromMe bool `json:"is_from_me"`
|
|
ChatJID string `json:"chat_jid"`
|
|
ID string `json:"id"`
|
|
ChatName string `json:"chat_name,omitempty"`
|
|
MediaType string `json:"media_type,omitempty"`
|
|
}
|
|
|
|
type Chat struct {
|
|
JID string `json:"jid"`
|
|
Name string `json:"name,omitempty"`
|
|
LastMessageTime time.Time `json:"last_message_time,omitempty"`
|
|
LastMessage string `json:"last_message,omitempty"`
|
|
LastSender string `json:"last_sender,omitempty"`
|
|
LastIsFromMe bool `json:"last_is_from_me,omitempty"`
|
|
}
|
|
|
|
func (c *Chat) IsGroup() bool {
|
|
return strings.HasSuffix(c.JID, "@g.us")
|
|
}
|
|
|
|
type Contact struct {
|
|
PhoneNumber string `json:"phone_number"`
|
|
Name string `json:"name,omitempty"`
|
|
JID string `json:"jid"`
|
|
}
|
|
|
|
type MessageContext struct {
|
|
Message Message `json:"message"`
|
|
Before []Message `json:"before"`
|
|
After []Message `json:"after"`
|
|
}
|
|
|
|
type ListMessagesParams struct {
|
|
After, Before string
|
|
SenderPhoneNumber *string
|
|
ChatJid *string
|
|
Query *string
|
|
Limit, Page int
|
|
IncludeContext bool
|
|
ContextBefore int
|
|
ContextAfter int
|
|
}
|
|
|
|
func SendMessage(recipient, message string) (bool, string) {
|
|
if recipient == "" {
|
|
return false, "Recipient must be provided"
|
|
}
|
|
|
|
token, err := GetOrRefreshJwtToken()
|
|
if err != nil {
|
|
return false, "Authentication failed: " + err.Error()
|
|
}
|
|
|
|
payload := map[string]string{
|
|
"recipient": recipient,
|
|
"message": message,
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", apiBaseURL+"/send", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return false, "Request error: " + err.Error()
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return false, fmt.Sprintf("HTTP %d - %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result map[string]interface{}
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return false, "Failed to parse response"
|
|
}
|
|
|
|
success, _ := result["success"].(bool)
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "Unknown response"
|
|
}
|
|
|
|
return success, msg
|
|
}
|
|
|
|
func SendFile(recipient, mediaPath string) (bool, string) {
|
|
if recipient == "" {
|
|
return false, "Recipient must be provided"
|
|
}
|
|
if mediaPath == "" {
|
|
return false, "Media path must be provided"
|
|
}
|
|
if _, err := os.Stat(mediaPath); os.IsNotExist(err) {
|
|
return false, "Media file not found: " + mediaPath
|
|
}
|
|
|
|
token, err := GetOrRefreshJwtToken()
|
|
if err != nil {
|
|
return false, "Authentication failed: " + err.Error()
|
|
}
|
|
|
|
payload := map[string]string{
|
|
"recipient": recipient,
|
|
"media_path": mediaPath,
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", apiBaseURL+"/send", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return false, "Request error: " + err.Error()
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return false, fmt.Sprintf("HTTP %d - %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result map[string]interface{}
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return false, "Failed to parse response"
|
|
}
|
|
|
|
success, _ := result["success"].(bool)
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "Unknown response"
|
|
}
|
|
|
|
return success, msg
|
|
}
|
|
|
|
func SendAudioVoiceMessage(recipient, mediaPath string) (bool, string) {
|
|
if recipient == "" {
|
|
return false, "Recipient must be provided"
|
|
}
|
|
if mediaPath == "" {
|
|
return false, "Media path must be provided"
|
|
}
|
|
if _, err := os.Stat(mediaPath); os.IsNotExist(err) {
|
|
return false, "Media file not found: " + mediaPath
|
|
}
|
|
|
|
finalPath := mediaPath
|
|
if !strings.HasSuffix(strings.ToLower(mediaPath), ".ogg") {
|
|
converted, err := ConvertToOpusOggTemp(mediaPath)
|
|
if err != nil {
|
|
return false, "Audio conversion failed (ffmpeg required?): " + err.Error()
|
|
}
|
|
finalPath = converted
|
|
defer func(name string) {
|
|
err := os.Remove(name)
|
|
if err != nil {
|
|
|
|
}
|
|
}(finalPath)
|
|
}
|
|
|
|
token, err := GetOrRefreshJwtToken()
|
|
if err != nil {
|
|
return false, "Authentication failed: " + err.Error()
|
|
}
|
|
|
|
payload := map[string]string{
|
|
"recipient": recipient,
|
|
"media_path": finalPath,
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", apiBaseURL+"/send", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return false, "Request error: " + err.Error()
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return false, fmt.Sprintf("HTTP %d - %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result map[string]interface{}
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return false, "Failed to parse response"
|
|
}
|
|
|
|
success, _ := result["success"].(bool)
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "Unknown response"
|
|
}
|
|
|
|
return success, msg
|
|
}
|
|
|
|
func DownloadMedia(messageID, chatJID string) (string, error) {
|
|
payload := map[string]string{
|
|
"message_id": messageID,
|
|
"chat_jid": chatJID,
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", apiBaseURL+"/download", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return "", fmt.Errorf("HTTP %d - %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var result map[string]interface{}
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if success, ok := result["success"].(bool); !ok || !success {
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "Unknown error"
|
|
}
|
|
return "", errors.New(msg)
|
|
}
|
|
|
|
path, ok := result["path"].(string)
|
|
if !ok || path == "" {
|
|
return "", errors.New("no path returned")
|
|
}
|
|
|
|
log.Printf("Media downloaded: %s", path)
|
|
return path, nil
|
|
}
|