Amarula.Contacts (amarula v0.4.0)

View Source

Contact discovery via USync. The consumer-facing half of Baileys' onWhatsApp / fetchStatus.

Both functions build a single USync iq (see Amarula.Protocol.USync), send it through the connection's generic IQ primitive (Amarula.Connection.query_iq/3), and turn the reply into friendly maps carrying Amarula.Address values — never raw jid strings, matching Amarula.Msg/Amarula.Group.

Call them on this module: on_whatsapp/2, fetch_status/2, resolve_lid/2.

Summary

Types

One resolve_lid/2 result: the contact's LID and PN addresses.

One on_whatsapp/2 result: the resolved address and whether it's on WhatsApp.

One fetch_status/2 result: the address, its status/bio text, and when it was set.

Functions

Fetch the status/bio text of the given users.

Look up a contact's LID from their PN in the local mapping store — the inverse of pn_for_lid/2. A cheap read; nil when unmapped.

Check which of the given phone numbers are on WhatsApp.

Look up a contact's PN from their LID in the local mapping store — a cheap read, no server query. Returns an Amarula.Address (:pn) or nil when the mapping isn't known yet.

Resolve each phone number to its privacy LID (<n>@lid) and persist the LID↔PN mapping, so the LID/PN mapping (and the Signal addressing the send pipeline uses) resolve that contact afterwards.

Types

conn()

@type conn() :: GenServer.server()

lid_pair()

@type lid_pair() :: %{lid: Amarula.Address.t(), pn: Amarula.Address.t()}

One resolve_lid/2 result: the contact's LID and PN addresses.

presence()

@type presence() :: %{address: Amarula.Address.t() | nil, exists: boolean()}

One on_whatsapp/2 result: the resolved address and whether it's on WhatsApp.

status()

@type status() :: %{
  address: Amarula.Address.t() | nil,
  status: String.t() | nil,
  set_at: DateTime.t() | nil
}

One fetch_status/2 result: the address, its status/bio text, and when it was set.

Functions

fetch_status(conn, jids)

@spec fetch_status(
  conn(),
  [String.t() | Amarula.Address.t()] | String.t() | Amarula.Address.t()
) ::
  {:ok, [status()]} | {:error, term()}

Fetch the status/bio text of the given users.

jids are wire jid strings or Amarula.Address values. Returns one result per user with the status text (nil when not visible to you, "" when explicitly empty) and the time it was set.

lid_for_pn(conn, pn)

@spec lid_for_pn(conn(), String.t() | Amarula.Address.t()) ::
  Amarula.Address.t() | nil

Look up a contact's LID from their PN in the local mapping store — the inverse of pn_for_lid/2. A cheap read; nil when unmapped.

on_whatsapp(conn, phones)

@spec on_whatsapp(conn(), [String.t()] | String.t()) ::
  {:ok, [presence()]} | {:error, term()}

Check which of the given phone numbers are on WhatsApp.

phones are bare numbers or +-prefixed (e.g. "15551234567"); each is sent as a USync contact lookup. Returns one result per number with the resolved Amarula.Address and an exists flag.

Amarula.Contacts.on_whatsapp(conn, ["15551234567"])
#=> {:ok, [%{address: %Amarula.Address{...}, exists: true}]}

pn_for_lid(conn, lid)

@spec pn_for_lid(conn(), String.t() | Amarula.Address.t()) ::
  Amarula.Address.t() | nil

Look up a contact's PN from their LID in the local mapping store — a cheap read, no server query. Returns an Amarula.Address (:pn) or nil when the mapping isn't known yet.

Amarula auto-populates this store from group metadata, the send pipeline, and resolve_lid/2. So after a :messages_upsert from a group member you can map their LID back to a PN without hitting WhatsApp — call resolve_lid/2 first if the mapping is missing.

Amarula.Contacts.pn_for_lid(conn, "12345@lid")
#=> %Amarula.Address{kind: :pn, ...} | nil

Replying to a LID-addressed message

You do not need the PN to replymsg.channel (a @lid address) is a valid send target; the send pipeline resolves the LID on the wire. Use this lookup only when your logic needs to identify the sender by phone number (Baileys #2205). For an inbound DM whose mapping isn't cached yet, run resolve_lid/2 once to populate it.

resolve_lid(conn, phones)

@spec resolve_lid(conn(), [String.t()] | String.t()) ::
  {:ok, [lid_pair()]} | {:error, term()}

Resolve each phone number to its privacy LID (<n>@lid) and persist the LID↔PN mapping, so the LID/PN mapping (and the Signal addressing the send pipeline uses) resolve that contact afterwards.

on_whatsapp/2 returns only the PN, so it can't establish a mapping; this runs a :lid+:contact USync (the only query that returns the pairing) and feeds the result into the same mapping store Amarula auto-populates from group metadata and the send pipeline. Returns one entry per number that resolved to a LID; numbers not on WhatsApp (no LID in the reply) are omitted.

Amarula.Contacts.resolve_lid(conn, ["15551234567"])
#=> {:ok, [%{lid: %Amarula.Address{kind: :lid, ...},
#           pn: %Amarula.Address{kind: :pn, ...}}]}