modified: .env
modified: bot.db modified: db/db.go modified: handlers/webhook.go modified: services/whatsapp.go
This commit is contained in:
4
.env
4
.env
@@ -1,2 +1,4 @@
|
|||||||
|
|
||||||
OPENROUTER_API_KEY=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
|
OPENROUTER_API_KEY=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
|
||||||
|
WHATSAPP_PHONE_ID=986583417873961
|
||||||
|
WHATSAPP_TOKEN=EAATqIU03y9YBQ5EBDxAFANmJIokKqjceliErZA1rYERpTzZBpRZAIKDVqlE2UWD0YUztSRwvsqjEdX2Uzt92Lsst5CEwcBiLZBbjiK8aAqpDclh2r2KyW5YvZCo7jXZAQf6dNYZABksjuxi5jUdLgfNQbhOhvSfv1z1qoWdZCUh9dUyzEcH3xmtCZBk9VG1qdtuLZBIlT335DSrSKgDpaLTBaWoe54aZCLwTxB89YZA78DkiIFmLq6dZBz3wSuUyMEfKokrRvtz7lXE6VkienXNucslgihZCkAMgZDZD
|
||||||
18
db/db.go
18
db/db.go
@@ -53,3 +53,21 @@ func SaveAppointment(phone string, date string) error {
|
|||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrCreateChatByPhone finds a chat for this phone number or creates one
|
||||||
|
func GetOrCreateChatByPhone(phone string) int {
|
||||||
|
// 1. Check if we already have a chat for this phone
|
||||||
|
// (Note: You might want to add a 'phone' column to 'chats' table if you haven't yet.
|
||||||
|
// For now, I'll cheat and put the phone in the title if it's new)
|
||||||
|
var id int
|
||||||
|
err := Conn.QueryRow("SELECT id FROM chats WHERE title = ?", phone).Scan(&id)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. If not found, create one
|
||||||
|
res, _ := Conn.Exec("INSERT INTO chats (title) VALUES (?)", phone)
|
||||||
|
newId, _ := res.LastInsertId()
|
||||||
|
return int(newId)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,38 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"whatsapp-bot/db"
|
||||||
|
"whatsapp-bot/services"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Meta verification
|
// Structs to parse incoming WhatsApp Webhook JSON
|
||||||
func VerifyWebhook(c *gin.Context) {
|
type WebhookPayload struct {
|
||||||
challenge := c.Query("hub.challenge")
|
Entry []struct {
|
||||||
token := c.Query("hub.verify_token")
|
Changes []struct {
|
||||||
|
Value struct {
|
||||||
|
Messages []struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
Text struct {
|
||||||
|
Body string `json:"body"`
|
||||||
|
} `json:"text"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"messages"`
|
||||||
|
} `json:"value"`
|
||||||
|
} `json:"changes"`
|
||||||
|
} `json:"entry"`
|
||||||
|
}
|
||||||
|
|
||||||
if token == "YOUR_SECRET_TOKEN" {
|
// VerifyWebhook (Keep this as is)
|
||||||
|
func VerifyWebhook(c *gin.Context) {
|
||||||
|
mode := c.Query("hub.mode")
|
||||||
|
token := c.Query("hub.verify_token")
|
||||||
|
challenge := c.Query("hub.challenge")
|
||||||
|
|
||||||
|
if mode == "subscribe" && token == "YOUR_SECRET_TOKEN" { // CHANGE THIS to match your Meta setup
|
||||||
c.String(http.StatusOK, challenge)
|
c.String(http.StatusOK, challenge)
|
||||||
} else {
|
} else {
|
||||||
c.Status(http.StatusForbidden)
|
c.Status(http.StatusForbidden)
|
||||||
@@ -19,7 +40,61 @@ func VerifyWebhook(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func HandleMessage(c *gin.Context) {
|
func HandleMessage(c *gin.Context) {
|
||||||
// You should bind your WhatsApp types here
|
var payload WebhookPayload
|
||||||
// go services.Process(...)
|
if err := c.BindJSON(&payload); err != nil {
|
||||||
c.Status(http.StatusOK)
|
// WhatsApp sends other events (statuses) that might not match. Ignore errors.
|
||||||
|
c.Status(200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Loop through messages (usually just one)
|
||||||
|
for _, entry := range payload.Entry {
|
||||||
|
for _, change := range entry.Changes {
|
||||||
|
for _, msg := range change.Value.Messages {
|
||||||
|
|
||||||
|
// We only handle text for now
|
||||||
|
if msg.Type != "text" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
userPhone := msg.From
|
||||||
|
userText := msg.Text.Body
|
||||||
|
|
||||||
|
fmt.Printf("📩 Received from %s: %s\n", userPhone, userText)
|
||||||
|
|
||||||
|
// 2. Identify the Chat Logic
|
||||||
|
chatID := db.GetOrCreateChatByPhone(userPhone)
|
||||||
|
|
||||||
|
// 3. Save User Message
|
||||||
|
db.Conn.Exec("INSERT INTO messages (chat_id, role, content) VALUES (?, 'user', ?)", chatID, userText)
|
||||||
|
|
||||||
|
// 4. Get AI Response
|
||||||
|
// Fetch history
|
||||||
|
rows, _ := db.Conn.Query("SELECT role, content FROM messages WHERE chat_id = ? ORDER BY created_at ASC", chatID)
|
||||||
|
var history []services.Message
|
||||||
|
for rows.Next() {
|
||||||
|
var m services.Message
|
||||||
|
rows.Scan(&m.Role, &m.Content)
|
||||||
|
history = append(history, m)
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
// Call AI (We don't need the stream callback here, just the final string)
|
||||||
|
aiResponse, _ := services.StreamAIResponse(history, func(chunk string) {
|
||||||
|
// We can't stream to WhatsApp, so we do nothing here.
|
||||||
|
})
|
||||||
|
|
||||||
|
// 5. Save & Send Response
|
||||||
|
if aiResponse != "" {
|
||||||
|
db.Conn.Exec("INSERT INTO messages (chat_id, role, content) VALUES (?, 'assistant', ?)", chatID, aiResponse)
|
||||||
|
err := services.SendWhatsAppMessage(userPhone, aiResponse)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("❌ Error sending to WhatsApp:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(200)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,43 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SendWhatsAppMessage sends a text reply to a user
|
||||||
|
func SendWhatsAppMessage(toPhone string, messageBody string) error {
|
||||||
|
token := os.Getenv("WHATSAPP_TOKEN") // "EAA..."
|
||||||
|
phoneID := os.Getenv("WHATSAPP_PHONE_ID") // "100..."
|
||||||
|
version := "v17.0"
|
||||||
|
url := fmt.Sprintf("https://graph.facebook.com/%s/%s/messages", version, phoneID)
|
||||||
|
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"messaging_product": "whatsapp",
|
||||||
|
"to": toPhone,
|
||||||
|
"type": "text",
|
||||||
|
"text": map[string]string{
|
||||||
|
"body": messageBody,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, _ := json.Marshal(payload)
|
||||||
|
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
return fmt.Errorf("whatsapp api error: status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user