fix(bridge): persist shared contact cards (vCards) as searchable text - #214
fix(bridge): persist shared contact cards (vCards) as searchable text#214SvenNico wants to merge 1 commit into
Conversation
|
Independently hit this on v0.6.0 and arrived at the same diagnosis and the same design call (store in Repro evidenceThe failure is quieter than "the contact doesn't render" — the row is never written at all, so a reaction to a shared contact leaves an orphaned pointer. In my store: select id, content, timestamp, filename from messages
where media_type='reaction' and filename='ACDD6CF9AF049962D3A96F1822B152A4';
-- 2A6D6FF59E49ECEB1F6F | 🙏 | 2026-08-26 18:20:28+03:00 | ACDD6CF9AF049962D3A96F1822B152A4
select count(*) from messages r where r.media_type='reaction'
and not exists (select 1 from messages m where m.id = r.filename);(Upper bound only — pre-history-window messages orphan reactions too.) Gap: the history-sync path is untouched
// Extract text content
var content string
if msg.Message.Message != nil {
if conv := msg.Message.Message.GetConversation(); conv != "" {
content = conv
} else if ext := msg.Message.Message.GetExtendedTextMessage(); ext != nil {
content = ext.GetText()
}
}then hits the same gate at line 3429. So contact cards arriving through history sync — a freshly paired device, or the on-demand per-chat sync from #168 — are still dropped after this PR. It also silently drops media captions and hydrated templates, which the live path has handled for a while; the two sites have quietly diverged. One line fixes it and removes the divergence permanently: content := extractTextContent(msg.Message.Message)
Edge case: degenerate cards store a placeholder row
if contact := msg.GetContactMessage(); contact != nil {
if body := formatContactContent(contact.GetDisplayName(), contact.GetVcard()); body != "" {
return "📇 " + body
}
}HousekeepingThe diff currently reads as +14,811/−1,019 across 60 files, with commits going back to Running the vCard fix plus the history-sync line locally on v0.6.0: |
ContactMessage and ContactsArrayMessage carry their vCard inline (no CDN payload), so extractTextContent and extractMediaInfo both returned empty and the message was silently dropped at the no-content/no-media gate in handleMessage — shared contacts never reached messages.db. Because the row is never written, a reaction to a shared contact is also left as an orphaned pointer. Store them as searchable text instead: display name plus every TEL value from the vCard body, e.g. "📇 John Doe (+62 812..., +47 22...)". Handles iPhone-style grouped properties (item1.TEL;...) and CRLF vCards. A card with neither a display name nor a TEL line yields "" so it still hits the gate rather than writing a "📇 " placeholder row. Also route the history-sync message loop through extractTextContent instead of its own inline Conversation/ExtendedText check, so contact cards (and media captions and hydrated templates, which the live path already handled) are surfaced when they arrive via history sync — a freshly paired device, or the on-demand per-chat sync from verygoodplugins#168. Thanks to @GeRryCh for independently reproducing this and pointing out the history-sync gap and the degenerate-card edge case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fd2d8a3 to
56b5e2f
Compare
|
Thanks for the independent repro and the detailed review — all three points landed. I've force-pushed a rebase onto current Folded in:
I left the orphaned-reaction regression query out of the Go tests since it needs a populated store — happy to add it as a script-level check if you think it's worth it. Thanks for the offer on the history-sync follow-up; folding it in here seemed cleaner given it's one line. |
Problem
Shared contact cards (
ContactMessage/ContactsArrayMessage) are silently dropped by the bridge. Their vCard payload is embedded inline in the message — there is no CDN URL/MediaKey — so bothextractTextContentandextractMediaInforeturn empty, and the message is discarded at the no-content/no-media gate inhandleMessage. The contact never reachesmessages.db, and nothing downstream (MCP server, webhooks) can recover it.Fix
Store contact cards as searchable text in
content:📇 John Doe (+62 812-3456-7890, +47 22 33 44 55)— display name plus every TEL value from the vCard (the vCard is the only copy we ever get, so no number is thrown away).📇 2 contacts shared: John Doe (+1 111); Jane Doe.item1.TEL;waid=...:...) and CRLF line endings — both verified against real WhatsApp traffic, where the grouped form is what iOS actually sends.No
media_typeis introduced: there is no downloadable payload, so treating it as media would ripple into the download endpoint and MCP models for no benefit. Quoted contact cards also render now, sinceextractQuotedMessageInforeusesextractTextContent.Testing
TestExtractTextContent_SurfacesMediaCaptionscovering single contact, grouped TEL, multiple TELs, no TEL, contact arrays, and CRLF vCards; fullgo test ./...green.messages.dbas📇 <name> (<number>).🤖 Generated with Claude Code