Amarula.Protocol.Messages.MessageEncoder (amarula v0.4.1)

View Source

Encode an outgoing Proto.Message to the padded plaintext bytes that the Signal cipher encrypts, ported from Baileys encodeWAMessage (src/Utils/generics.ts):

encodeWAMessage(m) = writeRandomPadMax16(proto.Message.encode(m))

writeRandomPadMax16 appends padLength bytes each equal to padLength, where padLength = (rand_byte & 0x0f) + 1 (1..16). unpadRandomMax16 (the receive side) reads the last byte as the pad length and strips it.

Summary

Functions

Build an album parent message announcing how many images/videos follow. The children are sent afterwards, each referencing this message's key as their album parent (see media/3's :album_parent opt).

Build a contact message from a display_name + vcard string. Pass a list of {display_name, vcard} to build a contactsArrayMessage (multiple contacts).

Build a %Proto.ContextInfo{} from reply/mention opts, or nil when neither is present (so a message stays a plain conversation/bare media)

Edit the message identified by target_key, replacing its body with new_text. Sent to target_key.remoteJid with the edit="1" stanza attr (added by the send path); the recipient replaces the original message's content.

Encode a %Proto.Message{} (or message map) and append random PKCS-style pad.

Build an event message. name is the event title. opts

Build a group-invite message — a sendable chat card that lets the recipient join group_jid via code (from Amarula.Group.invite_code/2). opts: :group_name, :caption, :expiration (unix ms when the code expires).

A PEER_DATA_OPERATION on-demand history request — sent to our own devices to ask the phone for older messages of a chat (Baileys fetchMessageHistory). oldest_key is the oldest message we already have; the phone replies with an ON_DEMAND HistorySync notification carrying up to count older messages.

Convenience: media(:image, info, opts).

Keep (keep?: true, KEEP_FOR_ALL) or undo-keep (false, UNDO_KEEP_FOR_ALL) the message identified by target_key — for messages in a disappearing chat.

Build a location message. lat/lng are floats (proto double). opts: :name, :address, :url, :is_live.

Build a media message of type (:image/:video/:audio/:document/ :sticker) from an encrypt+upload result. info carries the uploaded :url/:direct_path, the encrypt result (:media_key, :file_sha256, :file_enc_sha256, :file_length) and :mimetype. opts adds per-type extras (:caption, :width, :height, :seconds, :ptt, :file_name, :title).

Set (or, with "", clear) your own member tag in a group — the per-group self-label shown next to your name. Capped at 30 characters (WhatsApp's limit). Sent to the group as a GROUP_MEMBER_LABEL_CHANGE protocol message.

Pin (pin?: true) or unpin (false) the message identified by target_key for everyone in the chat (PIN_FOR_ALL / UNPIN_FOR_ALL).

A PEER_DATA_OPERATION placeholder-resend request for message_key — sent to our own devices to ask the phone to re-deliver that message (Baileys requestPlaceholderResend).

Build a poll-creation message. name is the question, options the choice strings. opts

Build a pollUpdateMessage casting a vote on an existing poll. Args

Build a reaction to the message identified by target_key. emoji is the reaction (e.g. "👍"); pass "" to REMOVE a previous reaction (Baileys semantics).

Build a delete-for-everyone (revoke) of the message identified by target_key. Sent to target_key.remoteJid; the recipient replaces the message with a "this message was deleted" tombstone.

Build a text message. With no :quoted/:mentions context it's a plain conversation; when a reply or mentions are present WhatsApp requires an extendedTextMessage (which carries the contextInfo), so we switch to that.

Functions

album(image_count, video_count)

@spec album(non_neg_integer(), non_neg_integer()) ::
  Amarula.Protocol.Proto.Message.t()

Build an album parent message announcing how many images/videos follow. The children are sent afterwards, each referencing this message's key as their album parent (see media/3's :album_parent opt).

contact(display_name, vcard)

@spec contact(String.t(), String.t()) :: Amarula.Protocol.Proto.Message.t()

Build a contact message from a display_name + vcard string. Pass a list of {display_name, vcard} to build a contactsArrayMessage (multiple contacts).

contacts(display_name, pairs)

@spec contacts(String.t(), [{String.t(), String.t()}, ...]) ::
  Amarula.Protocol.Proto.Message.t()

context_info(opts)

@spec context_info(keyword()) :: Amarula.Protocol.Proto.ContextInfo.t() | nil

Build a %Proto.ContextInfo{} from reply/mention opts, or nil when neither is present (so a message stays a plain conversation/bare media):

  • :quoted — the message being replied to, either:
    • an %Amarula.Msg{} — fills stanzaId, participant, and inlines the original's proto as quotedMessage (a full quote — renders even if the recipient no longer has the original), or
    • a {msg_id, participant} tuple (participant a jid or %Amarula.Address{}) — a lightweight quote: threads by stanzaId + participant with no inline quotedMessage. Use this when you have the id but not the original message. It renders when the recipient still has the quoted message; if they don't, the reply may show without a quote preview.
  • :mentions — a list of jids or %Amarula.Address{} to tag (mentionedJid).

edit(target_key, new_text)

@spec edit(Amarula.Protocol.Proto.MessageKey.t(), String.t()) ::
  Amarula.Protocol.Proto.Message.t()

Edit the message identified by target_key, replacing its body with new_text. Sent to target_key.remoteJid with the edit="1" stanza attr (added by the send path); the recipient replaces the original message's content.

encode(message)

@spec encode(struct() | map()) :: binary()

Encode a %Proto.Message{} (or message map) and append random PKCS-style pad.

event(name, opts \\ [])

@spec event(
  String.t(),
  keyword()
) :: Amarula.Protocol.Proto.Message.t()

Build an event message. name is the event title. opts:

  • :description — free text.
  • :location{lat, lng} floats, or a keyword with :name/:address.
  • :join_link — a call/meeting URL.
  • :start_time / :end_time — unix seconds.
  • :extra_guests_allowed — boolean.

group_invite(group_jid, code, opts \\ [])

@spec group_invite(String.t(), String.t(), keyword()) ::
  Amarula.Protocol.Proto.Message.t()

Build a group-invite message — a sendable chat card that lets the recipient join group_jid via code (from Amarula.Group.invite_code/2). opts: :group_name, :caption, :expiration (unix ms when the code expires).

history_sync_on_demand_request(oldest_key, oldest_ts, count)

@spec history_sync_on_demand_request(
  Amarula.Protocol.Proto.MessageKey.t(),
  integer(),
  non_neg_integer()
) :: Amarula.Protocol.Proto.Message.t()

A PEER_DATA_OPERATION on-demand history request — sent to our own devices to ask the phone for older messages of a chat (Baileys fetchMessageHistory). oldest_key is the oldest message we already have; the phone replies with an ON_DEMAND HistorySync notification carrying up to count older messages.

image(info, opts \\ [])

@spec image(
  map(),
  keyword()
) :: Amarula.Protocol.Proto.Message.t()

Convenience: media(:image, info, opts).

keep(target_key, keep?)

@spec keep(Amarula.Protocol.Proto.MessageKey.t(), boolean()) ::
  Amarula.Protocol.Proto.Message.t()

Keep (keep?: true, KEEP_FOR_ALL) or undo-keep (false, UNDO_KEEP_FOR_ALL) the message identified by target_key — for messages in a disappearing chat.

location(lat, lng, opts \\ [])

@spec location(float(), float(), keyword()) :: Amarula.Protocol.Proto.Message.t()

Build a location message. lat/lng are floats (proto double). opts: :name, :address, :url, :is_live.

media(type, info, opts \\ [])

@spec media(:image | :video | :audio | :document | :sticker, map(), keyword()) ::
  Amarula.Protocol.Proto.Message.t()

Build a media message of type (:image/:video/:audio/:document/ :sticker) from an encrypt+upload result. info carries the uploaded :url/:direct_path, the encrypt result (:media_key, :file_sha256, :file_enc_sha256, :file_length) and :mimetype. opts adds per-type extras (:caption, :width, :height, :seconds, :ptt, :file_name, :title).

member_label(label)

@spec member_label(String.t()) :: Amarula.Protocol.Proto.Message.t()

Set (or, with "", clear) your own member tag in a group — the per-group self-label shown next to your name. Capped at 30 characters (WhatsApp's limit). Sent to the group as a GROUP_MEMBER_LABEL_CHANGE protocol message.

pin(target_key, pin?)

@spec pin(Amarula.Protocol.Proto.MessageKey.t(), boolean()) ::
  Amarula.Protocol.Proto.Message.t()

Pin (pin?: true) or unpin (false) the message identified by target_key for everyone in the chat (PIN_FOR_ALL / UNPIN_FOR_ALL).

placeholder_resend_request(message_key)

@spec placeholder_resend_request(Amarula.Protocol.Proto.MessageKey.t()) ::
  Amarula.Protocol.Proto.Message.t()

A PEER_DATA_OPERATION placeholder-resend request for message_key — sent to our own devices to ask the phone to re-deliver that message (Baileys requestPlaceholderResend).

poll(name, options, opts \\ [])

@spec poll(String.t(), [String.t(), ...], keyword()) ::
  {Amarula.Protocol.Proto.Message.t(), binary()}

Build a poll-creation message. name is the question, options the choice strings. opts:

  • :selectable — max selectable options (0 = unlimited; default 1 = single).
  • :announcement — community-announcement-group poll (v2).
  • :message_secret — 32-byte vote-encryption key (default: random). Keep it; tallying incoming votes needs it (Poll.tally/3).

Picks the proto variant like Baileys: v2 for announcement, v3 for single-select, v1 for multi. Returns {message, message_secret}.

poll_vote(poll_key, creator_jid, voter_jid, message_secret, option_names)

@spec poll_vote(
  Amarula.Protocol.Proto.MessageKey.t(),
  String.t(),
  String.t(),
  binary(),
  [String.t()]
) ::
  Amarula.Protocol.Proto.Message.t()

Build a pollUpdateMessage casting a vote on an existing poll. Args:

  • poll_key — the %Proto.MessageKey{} of the poll-creation message.
  • creator_jid — the poll creator's jid (the poll message author).
  • voter_jid — our jid (the vote is encrypted per voter).
  • message_secret — the poll's 32-byte secret (returned by poll/3 at creation, or decoded from the poll's messageContextInfo).
  • option_names — the chosen option strings (must match the poll's options).

The vote is AES-256-GCM encrypted under the poll secret (PollCrypto), so it round-trips through the same decrypt path tallies use.

reaction(target_key, emoji)

@spec reaction(Amarula.Protocol.Proto.MessageKey.t(), String.t()) ::
  Amarula.Protocol.Proto.Message.t()

Build a reaction to the message identified by target_key. emoji is the reaction (e.g. "👍"); pass "" to REMOVE a previous reaction (Baileys semantics).

revoke(target_key)

@spec revoke(Amarula.Protocol.Proto.MessageKey.t()) ::
  Amarula.Protocol.Proto.Message.t()

Build a delete-for-everyone (revoke) of the message identified by target_key. Sent to target_key.remoteJid; the recipient replaces the message with a "this message was deleted" tombstone.

text(body, opts \\ [])

@spec text(
  String.t(),
  keyword()
) :: Amarula.Protocol.Proto.Message.t()

Build a text message. With no :quoted/:mentions context it's a plain conversation; when a reply or mentions are present WhatsApp requires an extendedTextMessage (which carries the contextInfo), so we switch to that.