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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ systray/systray
result
.pkg-config-wrapped
.wails-wrapped
*.AppImage
*.AppImage
*.exe
32 changes: 32 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,41 @@ func (a *Api) resyncAppState() {
// re-applies the collection from a server snapshot; with
// EmitAppStateEventsOnFullSync set, every mutation is dispatched to
// mainEventHandler (FromFullSync=true) and lands in our tables.
//
// critical_block and critical_unblock_low are NOT full-synced here —
// those collections are huge and fullSync destroys the version before the
// network round-trip completes, so a failure wipes the version entirely.
// Instead, critical_unblock_low is fetched without fullSync (preserving
// any existing mutation MACs); if its version was never stored (0), the
// internal fullSync code path still fetches the full snapshot.
//
// This is necessary because whatsmeow's auto-sync in
// handleAppStateSyncKeyShare only fires during initial pairing or key
// renewal — NOT on every connect.
for _, name := range []appstate.WAPatchName{appstate.WAPatchRegularLow, appstate.WAPatchRegularHigh} {
log.Printf("Starting app state full sync for %s", name)
if err := a.waClient.FetchAppState(a.ctx, name, true, false); err != nil {
log.Printf("App state full sync failed for %s: %v", name, err)
continue
}
log.Println("App state fully synced:", name)
}
// Sync critical_unblock_low (phone contacts — first_name/full_name)
// without fullSync to avoid destroying existing mutation MACs.
log.Printf("Starting app state sync for %s", appstate.WAPatchCriticalUnblockLow)
if err := a.waClient.FetchAppState(a.ctx, appstate.WAPatchCriticalUnblockLow, false, false); err != nil {
log.Printf("App state sync failed for %s: %v", appstate.WAPatchCriticalUnblockLow, err)
}
// Log contact sync status for diagnostics
if versions, _, err := a.waClient.Store.AppState.GetAppStateVersion(a.ctx, string(appstate.WAPatchCriticalUnblockLow)); err == nil && versions > 0 {
contacts, _ := a.waClient.Store.Contacts.GetAllContacts(a.ctx)
log.Printf("Contacts synced: %d entries (version %d)", len(contacts), versions)
} else if err != nil {
log.Printf("Contact sync version check failed: %v", err)
} else {
log.Printf("Contact sync has never completed (version 0)")
}
log.Println("Resync complete, emitting chat_list_refresh")
runtime.EventsEmit(a.ctx, "wa:chat_list_refresh")
}

Expand Down Expand Up @@ -534,6 +562,10 @@ func (a *Api) mainEventHandler(evt any) {
"chatId": v.Chat.String(),
"status": v.Type.GoString(),
})
case *events.Contact:
runtime.EventsEmit(a.ctx, "wa:chat_list_refresh")
case *events.PushName, *events.BusinessName:
runtime.EventsEmit(a.ctx, "wa:chat_list_refresh")
default:
// Ignore other events for now
}
Expand Down
19 changes: 18 additions & 1 deletion api/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,25 @@ import (
"log"
"time"

"github.com/nyaruka/phonenumbers"
"github.com/wailsapp/wails/v2/pkg/runtime"
"go.mau.fi/whatsmeow/appstate"
"go.mau.fi/whatsmeow/types"
)

// formatPhone formats a JID user component as an international phone number.
// Returns empty string if the JID isn't a phone number (e.g. LID, group, etc.)
func formatPhone(user string) string {
if user == "" {
return ""
}
num, err := phonenumbers.Parse("+"+user, "")
if err != nil || !phonenumbers.IsValidNumber(num) {
return ""
}
return phonenumbers.Format(num, phonenumbers.INTERNATIONAL)
}

type ChatElement struct {
LatestMessage string `json:"latest_message"`
LatestTS int64
Expand Down Expand Up @@ -104,20 +118,23 @@ func (a *Api) GetChatList() ([]ChatElement, error) {
FullName: name,
}
} else {
contact, err := a.waClient.Store.Contacts.GetContact(a.ctx, cm.JID)
contact, err := a.waClient.Store.Contacts.GetContact(a.ctx, cm.JID.ToNonAD())
phone := formatPhone(cm.JID.User)
if err != nil {
// Same here: degrade to the JID rather than failing everything.
log.Println("GetChatList: contact lookup failed, using fallback:", cm.JID.String(), err)
fc = Contact{
JID: cm.JID.String(),
PushName: cm.JID.User,
Phno: phone,
}
} else {
fc = Contact{
JID: cm.JID.String(),
Short: contact.FirstName,
FullName: contact.FullName,
PushName: contact.PushName,
Phno: phone,
IsBusiness: contact.BusinessName != "",
}
}
Expand Down
7 changes: 5 additions & 2 deletions api/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,13 @@ func (a *Api) FetchContacts() ([]Contact, error) {
}
contacts := make([]Contact, 0, len(rawContacts))
for jid, c := range rawContacts {
// Only return contacts that have valid phone numbers (skip LID-only entries)
if jid.Server != types.DefaultUserServer {
continue
}
rawNum := "+" + jid.User
// Parse phone number to use as International Format
num, err := phonenumbers.Parse(rawNum, "")
if err != nil && !phonenumbers.IsValidNumber(num) {
if err != nil || !phonenumbers.IsValidNumber(num) {
continue
}

Expand Down