Skip to content

Whatsmeow#15

Merged
ashiven merged 6 commits into
mainfrom
whatsmeow
Jan 10, 2026
Merged

Whatsmeow#15
ashiven merged 6 commits into
mainfrom
whatsmeow

Conversation

@ashiven
Copy link
Copy Markdown
Owner

@ashiven ashiven commented Jan 10, 2026

  • Added support for the whatsmeow api

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds WhatsApp messaging support to the household planner application using the whatsmeow library as an alternative to Twilio SMS. The implementation includes a new command-line flag to toggle between WhatsApp and SMS messaging.

Changes:

  • Added whatsmeow integration with QR code authentication and message sending capabilities
  • Refactored field naming from Phonenumber to PhoneNumber for consistency
  • Updated Dockerfile to support CGO and SQLite dependencies required by whatsmeow

Reviewed changes

Copilot reviewed 8 out of 10 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
pkg/planner/whatsmeow.go New file implementing WhatsApp client initialization, authentication, and phone number verification
pkg/planner/message.go Updated field references and exported CreateDailyTaskMessage function for use in main.go
pkg/planner/household.go Renamed Phonenumber field to PhoneNumber and added PhoneNumbers() method
pkg/planner/assignments.go Refactored receiver variable names from household to h for consistency
pkg/backend/handlers.go Updated field reference to use PhoneNumber
main.go Added WhatsApp messaging logic with -w flag, slices usage, and message sending loop
go.mod/go.sum Added whatsmeow and related dependencies
Dockerfile.backend Enabled CGO and added SQLite support for whatsmeow database
.gitignore Added devicestore.db to ignored files

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/planner/whatsmeow.go
Comment on lines +38 to +42
qrChannel, _ := client.GetQRChannel(context.Background())
err := client.Connect()
if err != nil {
fmt.Println("[ERROR] Login: ", err)
}
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return value from GetQRChannel is ignored. The second return value (error) should be checked before proceeding with the login process.

Suggested change
qrChannel, _ := client.GetQRChannel(context.Background())
err := client.Connect()
if err != nil {
fmt.Println("[ERROR] Login: ", err)
}
qrChannel, err := client.GetQRChannel(context.Background())
if err != nil {
fmt.Println("[ERROR] Login: ", err)
return
}
err = client.Connect()
if err != nil {
fmt.Println("[ERROR] Login: ", err)
return
}

Copilot uses AI. Check for mistakes.
Comment thread main.go
Comment thread main.go
Comment on lines +49 to +56
client := planner.NewWhatsmeowClient()

// NOTE: Needs QR Login via terminal on first startup
planner.Login(client)
time.Sleep(time.Second * 30)

phoneNumbers := myHousehold.PhoneNumbers()
JIDs := planner.PhoneNumbersToJIDs(client, phoneNumbers)
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling from NewWhatsmeowClient(), Login(), and PhoneNumbersToJIDs() is absent. Since these functions don't return errors (though they should), the code continues execution even if initialization fails. This could lead to nil pointer dereferences or unexpected behavior when trying to send messages.

Copilot uses AI. Check for mistakes.
Comment thread pkg/planner/whatsmeow.go
dbLogger := waLogger.Stdout("Database", "DEBUG", true)
ctx := context.Background()

container, err := sqlstore.New(ctx, "sqlite3", "file:devicestore.db?_foreign_keys=on", dbLogger)
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The database file path "file:devicestore.db?_foreign_keys=on" is hardcoded. This makes it difficult to configure for different environments (development, staging, production) and prevents users from specifying a custom location. Consider making this configurable through an environment variable or configuration file.

Suggested change
container, err := sqlstore.New(ctx, "sqlite3", "file:devicestore.db?_foreign_keys=on", dbLogger)
dbPath := os.Getenv("WHATSAPP_DEVICESTORE_DSN")
if dbPath == "" {
dbPath = "file:devicestore.db?_foreign_keys=on"
}
container, err := sqlstore.New(ctx, "sqlite3", dbPath, dbLogger)

Copilot uses AI. Check for mistakes.
Comment thread Dockerfile.backend
Comment thread pkg/planner/whatsmeow.go
Comment on lines +65 to +86
func PhoneNumbersToJIDs(client *whatsmeow.Client, phoneNumbers []string) map[string]types.JID {
JIDs := map[string]types.JID{}

isOnWhatsAppRes, err := client.IsOnWhatsApp(phoneNumbers)
if err != nil {
fmt.Println("[ERROR] IsOnWhatsApp: ", err)
}

for _, whatsAppRes := range isOnWhatsAppRes {
phoneNumber := "+" + whatsAppRes.JID.User

if whatsAppRes.IsIn {
fmt.Printf("[INFO] %s found on whatsapp\n", phoneNumber)
JIDs[phoneNumber] = whatsAppRes.JID

} else {
fmt.Printf("[INFO] %s is not on whatsapp\n", phoneNumber)
JIDs[phoneNumber] = types.JID{}
}
}
return JIDs
}
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function should return an error to allow proper error handling by callers. When client.IsOnWhatsApp() fails, the error is only logged and the function continues, potentially returning incomplete results. Callers cannot determine if the operation was successful.

Copilot uses AI. Check for mistakes.
Comment thread main.go
Comment on lines +49 to +56
client := planner.NewWhatsmeowClient()

// NOTE: Needs QR Login via terminal on first startup
planner.Login(client)
time.Sleep(time.Second * 30)

phoneNumbers := myHousehold.PhoneNumbers()
JIDs := planner.PhoneNumbersToJIDs(client, phoneNumbers)
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WhatsApp client is being created, authenticated, and phone numbers are being checked on every iteration of the infinite loop (daily). This is inefficient and wasteful - client initialization, login, and phone number verification should be performed once before entering the loop, not on every day's task assignment. Consider moving lines 49-56 outside the loop.

Copilot uses AI. Check for mistakes.
Comment thread main.go
Comment on lines +69 to +75
JID := JIDs[member.PhoneNumber]
waMessage := &waE2E.Message{Conversation: proto.String(message)}
_, err := client.SendMessage(context.Background(), JID, waMessage)
if err != nil {
fmt.Printf("[ERROR] Failed to deliver message to %s.\n", member.Name)
}
}
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a phone number is not on WhatsApp, an empty JID (types.JID{}) is stored in the map (line 82 in whatsmeow.go). In the message sending loop, this empty JID is then used to attempt sending a message without any validation. This will likely cause SendMessage to fail. Consider checking if the JID is valid before attempting to send, or filtering out members without valid JIDs.

Copilot uses AI. Check for mistakes.
Comment thread pkg/planner/whatsmeow.go
Comment on lines +16 to +33
func NewWhatsmeowClient() *whatsmeow.Client {
dbLogger := waLogger.Stdout("Database", "DEBUG", true)
ctx := context.Background()

container, err := sqlstore.New(ctx, "sqlite3", "file:devicestore.db?_foreign_keys=on", dbLogger)
if err != nil {
fmt.Println("[ERROR] NewWhatsmeowClient: ", err)
}

device, err := container.GetFirstDevice(ctx)
if err != nil {
fmt.Println("[ERROR] NewWhatsmeowClient: ", err)
}

clientLogger := waLogger.Stdout("Client", "INFO", true)
client := whatsmeow.NewClient(device, clientLogger)

return client
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error is not returned or properly handled. When creating the SQL store fails, the function continues execution with a nil container, which will cause a panic on the next line when calling container.GetFirstDevice(). The error should be returned to the caller for proper handling.

Suggested change
func NewWhatsmeowClient() *whatsmeow.Client {
dbLogger := waLogger.Stdout("Database", "DEBUG", true)
ctx := context.Background()
container, err := sqlstore.New(ctx, "sqlite3", "file:devicestore.db?_foreign_keys=on", dbLogger)
if err != nil {
fmt.Println("[ERROR] NewWhatsmeowClient: ", err)
}
device, err := container.GetFirstDevice(ctx)
if err != nil {
fmt.Println("[ERROR] NewWhatsmeowClient: ", err)
}
clientLogger := waLogger.Stdout("Client", "INFO", true)
client := whatsmeow.NewClient(device, clientLogger)
return client
func NewWhatsmeowClient() (*whatsmeow.Client, error) {
dbLogger := waLogger.Stdout("Database", "DEBUG", true)
ctx := context.Background()
container, err := sqlstore.New(ctx, "sqlite3", "file:devicestore.db?_foreign_keys=on", dbLogger)
if err != nil {
return nil, fmt.Errorf("NewWhatsmeowClient: failed to create SQL store: %w", err)
}
device, err := container.GetFirstDevice(ctx)
if err != nil {
return nil, fmt.Errorf("NewWhatsmeowClient: failed to get first device: %w", err)
}
clientLogger := waLogger.Stdout("Client", "INFO", true)
client := whatsmeow.NewClient(device, clientLogger)
return client, nil

Copilot uses AI. Check for mistakes.
Comment thread pkg/planner/whatsmeow.go
Comment on lines +16 to +33
func NewWhatsmeowClient() *whatsmeow.Client {
dbLogger := waLogger.Stdout("Database", "DEBUG", true)
ctx := context.Background()

container, err := sqlstore.New(ctx, "sqlite3", "file:devicestore.db?_foreign_keys=on", dbLogger)
if err != nil {
fmt.Println("[ERROR] NewWhatsmeowClient: ", err)
}

device, err := container.GetFirstDevice(ctx)
if err != nil {
fmt.Println("[ERROR] NewWhatsmeowClient: ", err)
}

clientLogger := waLogger.Stdout("Client", "INFO", true)
client := whatsmeow.NewClient(device, clientLogger)

return client
Copy link

Copilot AI Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function signature should return an error to allow proper error handling by callers. Currently, NewWhatsmeowClient returns only a *whatsmeow.Client, but errors from sqlstore.New() and container.GetFirstDevice() are only logged, not propagated. This prevents callers from handling initialization failures appropriately.

Suggested change
func NewWhatsmeowClient() *whatsmeow.Client {
dbLogger := waLogger.Stdout("Database", "DEBUG", true)
ctx := context.Background()
container, err := sqlstore.New(ctx, "sqlite3", "file:devicestore.db?_foreign_keys=on", dbLogger)
if err != nil {
fmt.Println("[ERROR] NewWhatsmeowClient: ", err)
}
device, err := container.GetFirstDevice(ctx)
if err != nil {
fmt.Println("[ERROR] NewWhatsmeowClient: ", err)
}
clientLogger := waLogger.Stdout("Client", "INFO", true)
client := whatsmeow.NewClient(device, clientLogger)
return client
func NewWhatsmeowClient() (*whatsmeow.Client, error) {
dbLogger := waLogger.Stdout("Database", "DEBUG", true)
ctx := context.Background()
container, err := sqlstore.New(ctx, "sqlite3", "file:devicestore.db?_foreign_keys=on", dbLogger)
if err != nil {
fmt.Println("[ERROR] NewWhatsmeowClient: ", err)
return nil, err
}
device, err := container.GetFirstDevice(ctx)
if err != nil {
fmt.Println("[ERROR] NewWhatsmeowClient: ", err)
return nil, err
}
clientLogger := waLogger.Stdout("Client", "INFO", true)
client := whatsmeow.NewClient(device, clientLogger)
return client, nil

Copilot uses AI. Check for mistakes.
@ashiven ashiven merged commit f04024a into main Jan 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants