fix: resolve live LID messages before storage

This commit is contained in:
Peter Steinberger 2026-05-04 07:00:30 +01:00
parent 78794f9757
commit fca5b96138
No known key found for this signature in database
7 changed files with 81 additions and 2 deletions

View File

@ -38,6 +38,7 @@
- Send: persist retry-message plaintext so linked devices can decrypt retried messages. (#186 — thanks @SimDamDev)
- Store: use the XDG state directory on Linux by default, while keeping existing `~/.wacli` stores working. (#172, #164 — thanks @txhno)
- Sync: guard lazy WhatsApp client initialization against concurrent `OpenWA` calls. (#62 — thanks @thakoreh)
- Sync: resolve live `@lid` chat and sender JIDs to phone-number JIDs before storing messages. (#196 — thanks @mahidconseil)
- Sync: warn when encrypted reaction messages cannot be decrypted instead of dropping the failure silently. (#192 — thanks @matrixise and @dinakars777)
- Sync: keep `sync --once` idle timing focused on message/history events so connection chatter cannot hang exit. (#119 — thanks @jyothepro)
- Sync: start `sync --once` idle timing after the `Connected` event. (#171 — thanks @fuleinist)

View File

@ -27,6 +27,7 @@ type WAClient interface {
ReconnectWithBackoff(ctx context.Context, minDelay, maxDelay time.Duration) error
ResolveChatName(ctx context.Context, chat types.JID, pushName string) string
ResolveLIDToPN(ctx context.Context, jid types.JID) types.JID
GetContact(ctx context.Context, jid types.JID) (types.ContactInfo, error)
GetAllContacts(ctx context.Context) (map[types.JID]types.ContactInfo, error)

View File

@ -30,6 +30,7 @@ type fakeWA struct {
contacts map[types.JID]types.ContactInfo
groups map[types.JID]*types.GroupInfo
lids map[types.JID]types.JID
onDemandHistory func(lastKnown types.MessageInfo, count int) *events.HistorySync
}
@ -40,6 +41,7 @@ func newFakeWA() *fakeWA {
handlers: map[uint32]func(interface{}){},
contacts: map[types.JID]types.ContactInfo{},
groups: map[types.JID]*types.GroupInfo{},
lids: map[types.JID]types.JID{},
nextHandlerID: 1,
}
}
@ -125,6 +127,16 @@ func (f *fakeWA) ResolveChatName(ctx context.Context, chat types.JID, pushName s
return chat.String()
}
func (f *fakeWA) ResolveLIDToPN(ctx context.Context, jid types.JID) types.JID {
f.mu.Lock()
defer f.mu.Unlock()
if pn, ok := f.lids[jid.ToNonAD()]; ok {
pn.Device = jid.Device
return pn
}
return jid
}
func (f *fakeWA) GetContact(ctx context.Context, jid types.JID) (types.ContactInfo, error) {
f.mu.Lock()
defer f.mu.Unlock()

View File

@ -1,6 +1,10 @@
package app
import "go.mau.fi/whatsmeow/types"
import (
"context"
"go.mau.fi/whatsmeow/types"
)
func canonicalJID(jid types.JID) types.JID {
if jid.Server == types.DefaultUserServer {
@ -12,3 +16,7 @@ func canonicalJID(jid types.JID) types.JID {
func canonicalJIDString(jid types.JID) string {
return canonicalJID(jid).String()
}
func (a *App) canonicalStoreJID(ctx context.Context, jid types.JID) types.JID {
return canonicalJID(a.wa.ResolveLIDToPN(ctx, jid))
}

View File

@ -114,6 +114,7 @@ func chatKind(chat types.JID) string {
}
func (a *App) storeParsedMessage(ctx context.Context, pm wa.ParsedMessage) error {
pm.Chat = a.canonicalStoreJID(ctx, pm.Chat)
chatJID := canonicalJIDString(pm.Chat)
chatName := a.wa.ResolveChatName(ctx, pm.Chat, pm.PushName)
if err := a.db.UpsertChat(chatJID, chatKind(pm.Chat), chatName, pm.Timestamp); err != nil {
@ -144,7 +145,7 @@ func (a *App) storeParsedMessage(ctx context.Context, pm wa.ParsedMessage) error
senderJID := pm.SenderJID
if pm.SenderJID != "" {
if jid, err := types.ParseJID(pm.SenderJID); err == nil {
contactJID := canonicalJID(jid)
contactJID := a.canonicalStoreJID(ctx, jid)
senderJID = contactJID.String()
if info, err := a.wa.GetContact(ctx, contactJID); err == nil {
if name := wa.BestContactName(info); name != "" {

View File

@ -172,6 +172,45 @@ func TestStoreParsedMessageNormalizesDefaultUserADJIDs(t *testing.T) {
}
}
func TestStoreParsedMessageResolvesLIDChatAndSender(t *testing.T) {
a := newTestApp(t)
f := newFakeWA()
a.wa = f
lid := types.JID{User: "999123456789", Server: types.HiddenUserServer}
pn := types.JID{User: "15551234567", Server: types.DefaultUserServer}
f.lids[lid.ToNonAD()] = pn
f.contacts[pn.ToNonAD()] = types.ContactInfo{Found: true, FullName: "Alice"}
err := a.storeParsedMessage(context.Background(), wa.ParsedMessage{
Chat: lid,
ID: "m-lid",
SenderJID: lid.String(),
Timestamp: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
Text: "hello",
})
if err != nil {
t.Fatalf("storeParsedMessage: %v", err)
}
msg, err := a.db.GetMessage(pn.String(), "m-lid")
if err != nil {
t.Fatalf("GetMessage resolved chat: %v", err)
}
if msg.ChatJID != pn.String() {
t.Fatalf("ChatJID = %q, want %q", msg.ChatJID, pn.String())
}
if msg.SenderJID != pn.String() {
t.Fatalf("SenderJID = %q, want %q", msg.SenderJID, pn.String())
}
if msg.ChatName != "Alice" {
t.Fatalf("ChatName = %q, want Alice", msg.ChatName)
}
if _, err := a.db.GetMessage(lid.String(), "m-lid"); err == nil {
t.Fatalf("message was also stored under unresolved LID chat")
}
}
func TestSyncStoresDisplayText(t *testing.T) {
a := newTestApp(t)
f := newFakeWA()

View File

@ -289,6 +289,23 @@ func (c *Client) GetAllContacts(ctx context.Context) (map[types.JID]types.Contac
return cli.Store.Contacts.GetAllContacts(ctx)
}
func (c *Client) ResolveLIDToPN(ctx context.Context, jid types.JID) types.JID {
if jid.Server != types.HiddenUserServer {
return jid
}
c.mu.Lock()
cli := c.client
c.mu.Unlock()
if cli == nil || cli.Store == nil || cli.Store.LIDs == nil {
return jid
}
pn, err := cli.Store.LIDs.GetPNForLID(ctx, jid.ToNonAD())
if err != nil || pn.IsEmpty() {
return jid
}
return pn
}
func BestContactName(info types.ContactInfo) string {
if !info.Found {
return ""