From 9ff021879fea50142f55c8b0a2de5c2348973432 Mon Sep 17 00:00:00 2001 From: SekiDesu0 Date: Sun, 1 Mar 2026 08:40:30 -0300 Subject: [PATCH] modified: .env modified: bot.db modified: db/db.go modified: handlers/webhook.go modified: services/whatsapp.go --- .env | 4 +- bot.db | Bin 36864 -> 40960 bytes db/db.go | 18 +++++++++ handlers/webhook.go | 91 +++++++++++++++++++++++++++++++++++++++---- services/whatsapp.go | 42 ++++++++++++++++++++ 5 files changed, 146 insertions(+), 9 deletions(-) diff --git a/.env b/.env index 91c18ed..770653f 100644 --- a/.env +++ b/.env @@ -1,2 +1,4 @@ -OPENROUTER_API_KEY=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 \ No newline at end of file +OPENROUTER_API_KEY=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 +WHATSAPP_PHONE_ID=986583417873961 +WHATSAPP_TOKEN=EAATqIU03y9YBQ5EBDxAFANmJIokKqjceliErZA1rYERpTzZBpRZAIKDVqlE2UWD0YUztSRwvsqjEdX2Uzt92Lsst5CEwcBiLZBbjiK8aAqpDclh2r2KyW5YvZCo7jXZAQf6dNYZABksjuxi5jUdLgfNQbhOhvSfv1z1qoWdZCUh9dUyzEcH3xmtCZBk9VG1qdtuLZBIlT335DSrSKgDpaLTBaWoe54aZCLwTxB89YZA78DkiIFmLq6dZBz3wSuUyMEfKokrRvtz7lXE6VkienXNucslgihZCkAMgZDZD \ No newline at end of file diff --git a/bot.db b/bot.db index 7bf7bc1f004e3f8c68b335ee0edb43cc0632c979..514ac89f8809f75577576ac9360ce29b30fd8c8d 100644 GIT binary patch delta 1905 zcmaJ?L2MgE6kR86O5AieY3x8LC|^M#jgmTEubnzR5Cu@BK+~j63#~w<6YtnNWWD3e z%r>L~IcY0I522J5$4UqxajKw1;s6qbBK3sC1qpFLhzm!=sepQbKkKwgT;O7j$N$fN z@BR0`SKn`2{i^Mq{X4H1hLOAqlZh)g&VD%d=x%c?VZC8}Z_QgzT84SuylnnxePq6D zj&1aG_n5~N)VSJ*P&*z;&-pOTi;|H_OK|2rjcqyAL^Ps{+jk+V&{|0BX z7`(Ci)XARNR9AcZUGWnRigo3>@hzx_Lwt<^|SS* zbnO`3n2KbY^COXlb1jG05MrWxhV?C%&z?J%BjqE9AMSsQG-9rw@o_f1C`pJK&2Qb{F zP$S|zhZU%?D6!txP9;PiPo~o`bxgI#*Pfok-2MyGnEUrz>F9&Y{n4#gdq+yiq@95y z1S%ADhXd|6*NJ|I%Hd=#{=wsdB%KVLcz3PZ|1D6O00|I5dnQX~Nv5NyU zxa36zYPXV=irKbi$+H5=C{ukPALrr1zgZ;F6WG8V{5gtENL0tNnYZ$3roUf;QkZ zo}ljWfI<+~7N|%j=e89F;`^XHL9sGbg!vLAhiNDy1zGl}8~Sk#5fj?vlW9>7O`v`LPj__PHmuv$lGSH^ zZLW9S#;N5H((cHVTS~TiU_3=D5RT#>7g1-9iD*$Q=*I{ z4|5DBit&K>FvDHy50A#}3Kb7{jfa|lMJ*G2MjJ+UdMN6Go@3e%EI>1bWyL09ib$)U=kE29{+fqhnI{yIVxijGa delta 350 zcmZoTz|^pSX@WFsH3I_!=R^g2#_Ej;OZ25U`8pZ-Yx%G62lK1(Gw`kD>*Tx1znm|Q z&v>(-fGD4@CLbFEgQBi{Zdp=RS$0`QR<@CWk(sW6v96&(h=HM%fk|?HURq{RZfc4q zFCnFy8|*>_{RMfLnPnMsQ;Ule(^HGRfbQYqOwLFwDHdR4R%J{qD9F#uE6GjGD=FsV z<=e)<#W$6K|33dd{<-`${L%c9{Pui5_^$I!1;P@(U_K+DJNft~8^uX;SQ%Sb85qb5 zOnw+I&Shv|Wo&L`Y9`N*#5J*y=L2ykuZ!bmRGhppUKGe;V==Up=K%?C{tzd^$i>M2 z8|WqmPX6DU`3m0hZ~mRnDZs4G{CqNdga5|HiOihAUTh4E^0~%*lP4xfZsu(H&J6(5 C$za<6 diff --git a/db/db.go b/db/db.go index 4bc00a5..42ffff1 100644 --- a/db/db.go +++ b/db/db.go @@ -53,3 +53,21 @@ func SaveAppointment(phone string, date string) error { ) 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) +} diff --git a/handlers/webhook.go b/handlers/webhook.go index dd0a32c..02b4037 100644 --- a/handlers/webhook.go +++ b/handlers/webhook.go @@ -1,17 +1,38 @@ package handlers import ( + "fmt" "net/http" + "whatsapp-bot/db" + "whatsapp-bot/services" "github.com/gin-gonic/gin" ) -// Meta verification -func VerifyWebhook(c *gin.Context) { - challenge := c.Query("hub.challenge") - token := c.Query("hub.verify_token") +// Structs to parse incoming WhatsApp Webhook JSON +type WebhookPayload struct { + Entry []struct { + 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) } else { c.Status(http.StatusForbidden) @@ -19,7 +40,61 @@ func VerifyWebhook(c *gin.Context) { } func HandleMessage(c *gin.Context) { - // You should bind your WhatsApp types here - // go services.Process(...) - c.Status(http.StatusOK) + var payload WebhookPayload + if err := c.BindJSON(&payload); err != nil { + // 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) } diff --git a/services/whatsapp.go b/services/whatsapp.go index 5e568ea..39b0be2 100644 --- a/services/whatsapp.go +++ b/services/whatsapp.go @@ -1 +1,43 @@ 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 +}