imessage: backfill display names from vCard contacts#303
Conversation
roborev: Combined Review (
|
|
Pushed
Test ( |
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
|
Thanks for working on this, I hadn't yet looked into how to get people's names into the whatsapp/imessage screens. I need to see how this composes with the other open PRs so bear with me |
chat.db only stores phone/email handles, so iMessage-imported participants showed up as raw numbers/addresses unless another source (Gmail headers, Google Voice, WhatsApp --contacts) had previously populated their display_name. Two changes that together let names appear: - Stop poisoning display_name with the phone string when the iMessage importer creates a new participant. The previous behavior left a non-empty display_name = "+15551234567" that blocked later imports from filling in a real name (the update guard requires NULL/empty). Pass "" instead so first-name-bearing import wins. - Add --contacts <vcf> to import-imessage, mirroring WhatsApp's flag. Backfills participant names by phone and email from a vCard export (e.g. macOS Contacts.app → Export). Only updates participants that already exist and have an empty display_name, so prior names are preserved. Plumbing: - Extract the vCard parser into internal/vcard so iMessage and WhatsApp share it instead of cross-importing each other. EMAIL fields are now parsed in addition to TEL/FN. - Add Store.UpdateParticipantDisplayNameByEmail (case-insensitive, first-writer-wins, never creates rows). Additional changes squashed in: - imessage: clear legacy phone-as-name placeholders on backfill - imessage: refresh stale 1:1 conversation titles after import - imessage: force full cache rebuild after name/title backfill - vcard: stop eating END:VCARD when base64 PHOTO ends with '=' padding - imessage: refresh generated group chat titles - vcard: handle folded QP and grouped properties Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phone-only iMessage/SMS participants — display_name empty, email_address NULL, phone_number set — disappeared from sender/ recipient name views and filters because the name fallback chain stopped at email_address. Add a single helper participantNameExpr(alias) returning COALESCE(NULLIF(TRIM(display_name), ''), NULLIF(phone_number, ''), email_address) and route every aggregate keyExpr, null-guard, and SenderName/RecipientName filter through it. The existing text-search sites that already had the phone fallback (duckdb_text.go, sqlite_text.go) now use the helper too. ParticipantOpts grows a Phone field and TestDataBuilder gets AddPhoneParticipant; tests cover the SQLite aggregate, DuckDB aggregate, and both ListMessages name-filter paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SearchByDomains skipped store.LiveMessagesWhere, so the MCP
search_by_domains tool could surface dedup losers (deleted_at) and
source-deleted rows that every other read path suppresses.
Apply LiveMessagesWhere("m", true) — same predicate Search/SearchFast
use — so dedup losers are always hidden alongside source-deleted
rows. New regression test soft-deletes one message of each kind and
verifies neither leaks. Adds dbtest.MarkDedupLoserByID for setting
deleted_at directly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PST EntryIDs are unique only within a single archive, but the importer keys messages as "pst-<EntryID>" against a source shared by all PST files imported under the same identifier. Importing a second archive into the same mailbox could collide with rows from the first and silently skip or mis-update unrelated messages. Compute a stable per-archive fingerprint from a SHA-256 over the first 4 KiB of the PST file (the header, where unique BID/NID counters live) and prefix it on source_message_id: pst-<fp12>-<EntryID> Same bytes always yield the same fingerprint regardless of path, so re-importing the same file remains idempotent. Different files now sit in disjoint key spaces even when they share a source. Unit test exercises distinct/stable fingerprint behavior; the existing TestImportPst_SupportPST_Idempotent integration test continues to cover round-trip idempotence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9205e44 to
7081192
Compare
roborev: Combined Review (
|
Summary
iMessage-imported users showed up as raw phone numbers / emails because
chat.dbonly stores handles — names came through only when another source (Gmail headers, Google Voice, WhatsApp--contacts) had already populatedparticipants.display_name.Two changes that together let names appear:
Stop poisoning
display_namewith the phone string when the iMessage importer creates a new participant. The previous behavior leftdisplay_name = "+15551234567", which blocked later imports from filling in a real name (the update guard only writes whendisplay_nameisNULL/empty). New iMessage rows now leavedisplay_nameempty and let the first name-bearing import win.Add
--contacts <vcf>toimport-imessage, mirroring WhatsApp's flag. Backfills names by phone and email from a vCard export (e.g. macOS Contacts.app → File → Export → Export vCard). Only updates participants that already exist with an emptydisplay_name, so any prior name is preserved.Plumbing
internal/vcardpackage so iMessage and WhatsApp share it (whatsapp.ImportContactsnow wraps it). The parser also readsEMAILfields, not justTEL/FN.Store.UpdateParticipantDisplayNameByEmail— case-insensitive, first-writer-wins, never creates rows.Usage
Output now includes: