Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions whatsapp-bridge/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AUTH_SECRET=YOUR_SECRET
1 change: 1 addition & 0 deletions whatsapp-bridge/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 // indirect
Expand Down
2 changes: 2 additions & 0 deletions whatsapp-bridge/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
Expand Down
115 changes: 109 additions & 6 deletions whatsapp-bridge/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"database/sql"
"encoding/base64"
"encoding/binary"
"encoding/json"
"fmt"
Expand All @@ -18,6 +19,7 @@ import (
"time"

_ "github.com/mattn/go-sqlite3"
"github.com/joho/godotenv"
"github.com/mdp/qrterminal"

"bytes"
Expand All @@ -31,6 +33,8 @@ import (
"google.golang.org/protobuf/proto"
)

var authSecret string

// Message represents a chat message for our client
type Message struct {
Time time.Time
Expand Down Expand Up @@ -182,6 +186,24 @@ func (store *MessageStore) IsWhitelisted(jid string) bool {
return whitelisted
}

func (store *MessageStore) FindChatsByPattern(pattern string) ([]string, error) {
rows, err := store.db.Query("SELECT jid FROM chats WHERE LOWER(name) LIKE LOWER(?) OR jid LIKE ?", "%"+pattern+"%", "%"+pattern+"%")
if err != nil {
return nil, err
}
defer rows.Close()

var jids []string
for rows.Next() {
var jid string
if err := rows.Scan(&jid); err != nil {
continue
}
jids = append(jids, jid)
}
return jids, nil
}

func (store *MessageStore) SetWhitelist(jid string, whitelisted bool) error {
_, err := store.db.Exec("UPDATE chats SET whitelisted = ? WHERE jid = ?", whitelisted, jid)
return err
Expand Down Expand Up @@ -538,6 +560,7 @@ type DownloadMediaResponse struct {

type WhitelistRequest struct {
PhoneNumbers []string `json:"phone_numbers"`
Identifiers []string `json:"identifiers"`
}

type WhitelistResponse struct {
Expand Down Expand Up @@ -850,7 +873,7 @@ func startRESTServer(client *whatsmeow.Client, messageStore *MessageStore, port
})
})

http.HandleFunc("/api/whitelist/add", func(w http.ResponseWriter, r *http.Request) {
http.HandleFunc("/api/whitelist/add", authMiddleware(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
Expand All @@ -864,8 +887,8 @@ func startRESTServer(client *whatsmeow.Client, messageStore *MessageStore, port

w.Header().Set("Content-Type", "application/json")

if len(req.PhoneNumbers) == 0 {
http.Error(w, "phone_numbers is required", http.StatusBadRequest)
if len(req.PhoneNumbers) == 0 && len(req.Identifiers) == 0 {
http.Error(w, "phone_numbers or identifiers is required", http.StatusBadRequest)
return
}

Expand All @@ -880,6 +903,17 @@ func startRESTServer(client *whatsmeow.Client, messageStore *MessageStore, port
jidsToAdd = append(jidsToAdd, phoneToJID(phoneNumber))
}

for _, identifier := range req.Identifiers {
if strings.Contains(identifier, "@") {
jidsToAdd = append(jidsToAdd, identifier)
} else {
matchedJIDs, err := messageStore.FindChatsByPattern(identifier)
if err == nil {
jidsToAdd = append(jidsToAdd, matchedJIDs...)
}
}
}

if len(invalidNumbers) > 0 {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(WhitelistResponse{
Expand All @@ -889,6 +923,15 @@ func startRESTServer(client *whatsmeow.Client, messageStore *MessageStore, port
return
}

if len(jidsToAdd) == 0 {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(WhitelistResponse{
Success: false,
Message: "No matching chats found for the provided identifiers",
})
return
}

var failedJIDs []string
var successCount int

Expand All @@ -913,7 +956,7 @@ func startRESTServer(client *whatsmeow.Client, messageStore *MessageStore, port
Message: fmt.Sprintf("Successfully added %d contacts to whitelist", successCount),
})
}
})
}))

http.HandleFunc("/api/whitelist/remove", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
Expand All @@ -929,8 +972,8 @@ func startRESTServer(client *whatsmeow.Client, messageStore *MessageStore, port

w.Header().Set("Content-Type", "application/json")

if len(req.PhoneNumbers) == 0 {
http.Error(w, "phone_numbers is required", http.StatusBadRequest)
if len(req.PhoneNumbers) == 0 && len(req.Identifiers) == 0 {
http.Error(w, "phone_numbers or identifiers is required", http.StatusBadRequest)
return
}

Expand All @@ -945,6 +988,17 @@ func startRESTServer(client *whatsmeow.Client, messageStore *MessageStore, port
jidsToRemove = append(jidsToRemove, phoneToJID(phoneNumber))
}

for _, identifier := range req.Identifiers {
if strings.Contains(identifier, "@") {
jidsToRemove = append(jidsToRemove, identifier)
} else {
matchedJIDs, err := messageStore.FindChatsByPattern(identifier)
if err == nil {
jidsToRemove = append(jidsToRemove, matchedJIDs...)
}
}
}

if len(invalidNumbers) > 0 {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(WhitelistResponse{
Expand All @@ -954,6 +1008,15 @@ func startRESTServer(client *whatsmeow.Client, messageStore *MessageStore, port
return
}

if len(jidsToRemove) == 0 {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(WhitelistResponse{
Success: false,
Message: "No matching chats found for the provided identifiers",
})
return
}

var failedJIDs []string
var successCount int

Expand Down Expand Up @@ -1070,6 +1133,17 @@ func startRESTServer(client *whatsmeow.Client, messageStore *MessageStore, port
}

func main() {
err := godotenv.Load()
if err != nil {
fmt.Println("Warning: .env file not found, using environment variables")
}

authSecret = os.Getenv("AUTH_SECRET")
if authSecret == "" {
fmt.Println("Error: AUTH_SECRET environment variable is required")
return
}

// Set up logger
logger := waLog.Stdout("Client", "INFO", true)
logger.Infof("Starting WhatsApp client...")
Expand Down Expand Up @@ -1629,3 +1703,32 @@ func placeholderWaveform(duration uint32) []byte {

return waveform
}

func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

authHeader := r.Header.Get("Authorization")
if authHeader == "" {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "Unauthorized: missing authorization header",
})
return
}

decodedAuth, err := base64.StdEncoding.DecodeString(authHeader)

if err != nil || string(decodedAuth) != authSecret {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "Unauthorized: invalid credentials",
})
return
}

next.ServeHTTP(w, r)
}
}