### start_link(init_arg \\ []) Source: https://hexdocs.pm/amarula/Amarula.Supervisor.html Starts the shared supervision tree. ```APIDOC ## start_link(init_arg \\ []) ### Description Starts the shared tree. Pass no options; add `Amarula.Supervisor` to your children list in your supervision tree. ``` -------------------------------- ### make_socket(conn, opts) Source: https://hexdocs.pm/amarula/Amarula.Connection.html Starts a connection instance. ```APIDOC ## make_socket(conn, opts) ### Description Start a connection instance and return its pid. Starts the per-connection supervision tree. ### Parameters - **conn** (Amarula.Conn.t) - Required - The connection configuration. - **opts** (keyword) - Optional - Options, may carry `:parent_pid` (events sink, default the caller). ### Response - **{:ok, pid()} | {:error, {:already_running, pid()}} | {:error, term()}** - Returns the pid of the connection or an error. ``` -------------------------------- ### Run Amarula examples Source: https://hexdocs.pm/amarula/index.html Execute pairing and messaging examples using the mix run command. ```bash # Pair a device and listen (shows a QR, then prints incoming messages) mix run examples/pair.exs my_profile # Send one message through a supervised connection, then exit mix run examples/send_message.exs 5511999999999 "hello from amarula" ``` -------------------------------- ### Initialize and Configure MessageStore Source: https://hexdocs.pm/amarula/Amarula.Plugins.MessageStore.html Start the store and integrate it into the Amarula socket configuration. ```elixir {:ok, store} = Amarula.Plugins.MessageStore.start_link(dir: "./amarula_store") config = base_config |> Map.put(:get_message, Amarula.Plugins.MessageStore.get_message_fun(store)) # Make the store the socket's parent_pid so it sees the events directly, # or forward `{:amarula, :messages_upsert, ..}` to it from your own process. {:ok, socket} = Amarula.new(config) |> Amarula.connect(parent_pid: store) ``` -------------------------------- ### Quick Start Connection Source: https://hexdocs.pm/amarula/index.html Initialize a connection, handle the QR code authentication, and send a message. ```elixir # Start a connection. Events (the QR code, incoming messages) are delivered to # parent_pid — here, the current process. {:ok, conn} = Amarula.new(%{profile: :me}) |> Amarula.connect(parent_pid: self()) # First run: you get a QR code. Print it and scan it on your phone: # WhatsApp → Settings → Linked Devices → Link a device receive do {:amarula, :connection_update, %{qr: qr}} when is_binary(qr) -> {:ok, art} = Amarula.render_qr(qr) IO.puts(art) end # Once linked you get an :open update — now you can send. receive do {:amarula, :connection_update, %{connection: :open}} -> :ready end Amarula.send_text(conn, "5511999999999@s.whatsapp.net", "hello from Elixir!") ``` -------------------------------- ### Initialize connection socket Source: https://hexdocs.pm/amarula/Amarula.Connection.html Starts a connection instance and returns the PID of the connection process. ```elixir @spec make_socket( Amarula.Conn.t(), keyword() ) :: {:ok, pid()} | {:error, {:already_running, pid()}} | {:error, term()} ``` -------------------------------- ### start_link(opts \ []) Source: https://hexdocs.pm/amarula/Amarula.Plugins.MessageStore.html Starts the MessageStore GenServer. The :dir option specifies the directory for the DETS file storage. ```APIDOC ## start_link(opts \ []) ### Description Starts the store process. The :dir option sets the directory where the DETS file lives (defaults to ./amarula_store). ### Parameters - **opts** (keyword list) - Optional - Configuration options including :dir. ``` -------------------------------- ### Example usage of to_jid/1 Source: https://hexdocs.pm/amarula/Amarula.Address.html Demonstrates converting a phone number address to a WhatsApp JID string. ```elixir iex> Amarula.Address.to_jid(Amarula.Address.pn("5511999999999")) {:ok, "5511999999999@s.whatsapp.net"} ``` -------------------------------- ### Configure a clustered registry Source: https://hexdocs.pm/amarula/Amarula.ProfileRegistry.html Example of initializing a new connection with a cluster-wide registry using Horde. ```elixir Amarula.new(%{profile: :primary, registry: {Horde.Registry, MyApp.HordeReg}}) ``` -------------------------------- ### get(adapter_state, profile, namespace, key) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Fetches the value associated with the specified profile, namespace, and key. ```APIDOC ## get(adapter_state, profile, namespace, key) ### Description Fetch the value at {profile, namespace, key}. Returns :error on miss or corruption. ### Signature `get(adapter_state(), profile(), namespace(), key()) :: {:ok, term()} | :error` ``` -------------------------------- ### child_spec/1 Source: https://hexdocs.pm/amarula/Amarula.SupervisedConnection.html Returns a specification to start this module under a supervisor. ```APIDOC ## child_spec(init_arg) ### Description Returns a specification to start this module under a supervisor. This is typically used by adding `{Amarula, config}` to your supervision tree. ### Parameters - **init_arg** (term) - The configuration arguments required to initialize the supervised connection. ``` -------------------------------- ### Pairing via QR or Phone Code Source: https://hexdocs.pm/amarula/Mix.Tasks.Amarula.Pair.html Examples of using the mix task to initiate pairing for a specific profile or the guest profile. ```bash # QR: prints a scannable QR in your terminal mix amarula.pair mix amarula.pair guest # Phone code: prints an 8-char code to type into WhatsApp instead of scanning mix amarula.pair --phone mix amarula.pair guest --phone 5511999999999 ``` -------------------------------- ### Implement and attach a plugin Source: https://hexdocs.pm/amarula/Amarula.Plugin.html Example of a blocklist plugin that halts the send pipeline if the destination JID is in the provided list. ```elixir defmodule Blocklist do def attach(conn, opts \\ []) do jids = Keyword.get(opts, :jids, []) Amarula.Plugin.on_send(conn, fn %{to: to} = ctx -> if to in jids, do: {:halt, :blocked}, else: {:cont, ctx} end) end end Amarula.new(config) |> Blocklist.attach(jids: [...]) |> Amarula.connect() ``` -------------------------------- ### get(scope, profile, namespace, key) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Fetches the value at the specified profile, namespace, and key. Returns :error on miss or corruption. ```APIDOC ## get(scope, profile, namespace, key) ### Description Fetches the value at the specified profile, namespace, and key. Returns :error on miss or corruption. ### Signature `get(Amarula.Storage.Scope.t(), profile(), namespace(), key()) :: {:ok, term()} | :error` ``` -------------------------------- ### get(scope, profile, msg_id) Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.html Fetches a stored entry by ID. ```APIDOC ## get(scope, profile, msg_id) ### Description Fetches a stored entry by its ID, or returns `:error` if not found. ### Signature `get(Amarula.MessageSecretStore.Scope.t(), profile(), String.t()) :: {:ok, entry()} | :error` ``` -------------------------------- ### start_offline(opts) Source: https://hexdocs.pm/amarula/Amarula.Testing.html Starts an offline connection that is ready to receive injected messages without performing a websocket or Noise handshake. ```APIDOC ## start_offline(opts) ### Description Start an offline connection: :connected immediately, no websocket, no Noise handshake, ready to receive injected messages. ### Parameters - **opts** (keyword()) - Required - Options including :profile (required), :parent_pid, :frame_sink, :auth, and :storage. ``` -------------------------------- ### start_offline/1 Source: https://hexdocs.pm/amarula/Amarula.Testing.html Starts an offline connection handle, allowing for the injection of messages without a live websocket or handshake. ```APIDOC ## start_offline(opts) ### Description Starts an offline connection: :connected immediately, no websocket, no Noise handshake, ready to receive injected messages. ### Parameters - **opts** (keyword) - Configuration options for the connection. ``` -------------------------------- ### Supervision Tree Integration Source: https://hexdocs.pm/amarula/index.html Start Amarula accounts declaratively within your application's supervision tree. ```elixir children = [ Amarula.Supervisor, # Amarula's shared tree — you own it MyApp.WhatsAppRouter, # your event sink (a named process) {Amarula, profile: :sales, parent: MyApp.WhatsAppRouter}, {Amarula, profile: :support, parent: MyApp.WhatsAppRouter} ] Supervisor.start_link(children, strategy: :one_for_one) ``` -------------------------------- ### Example JID formats Source: https://hexdocs.pm/amarula/lid_pn.html Common JID string formats used by WhatsApp, including account-level PNs, device-specific PNs, LIDs, and group identifiers. ```text 5511999998888@s.whatsapp.net a PN, account-level 5511999998888:3@s.whatsapp.net the same PN, device 3 20000000001@lid a LID, account-level 120363000000000001@g.us a group ``` -------------------------------- ### Test a bot with an offline sandbox connection Source: https://hexdocs.pm/amarula/Amarula.Testing.html Demonstrates starting an offline connection, handling incoming messages, and delivering a test message to verify the bot's response. ```elixir {:ok, conn} = Amarula.Testing.start_offline(profile: :test) # The bot under test: replies "pong" to "ping". defp handle({:amarula, :messages_upsert, %{messages: msgs}}, conn) do for %Amarula.Msg{type: :text, content: "ping", channel: chan} <- msgs do Amarula.send_text(conn, Amarula.Address.to_jid!(chan), "pong") end end # Drive it: deliver an inbound message, let the bot react. Amarula.Testing.deliver_text(conn, from: "15551234567@s.whatsapp.net", text: "ping") assert_receive {:amarula, :messages_upsert, %{messages: [_]}} = event handle(event, conn) # The reply `Amarula.send_text(conn, ..., "pong")` returns `{:ok, id}` and # does nothing else — no real message is sent. ``` -------------------------------- ### Start offline connection for testing Source: https://hexdocs.pm/amarula/Amarula.Testing.html Initializes a connection in offline mode, bypassing websocket and handshake processes. Useful for unit testing bot logic with injected messages. ```elixir @spec start_offline(keyword()) :: {:ok, conn()} | {:error, term()} ``` -------------------------------- ### start_offline/1 Source: https://hexdocs.pm/amarula/Amarula.Testing.html Starts an offline sandbox connection for testing purposes. Returns the connection PID to be used with other Amarula functions. ```APIDOC ## start_offline(opts) ### Description Starts an offline sandbox connection. The connection never reaches WhatsApp, and outbound messages short-circuit to `{:ok, msg_id}`. ### Parameters - **opts** (keyword list) - Required - Configuration options, e.g., `[profile: :test]`. ### Returns - **pid** - The connection PID. ``` -------------------------------- ### Implement get callback Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.html Fetches a stored entry by message ID, returning :error if not found or expired. ```elixir @callback get(adapter_state(), profile(), msg_id :: String.t()) :: {:ok, entry()} | :error ``` -------------------------------- ### new(opts) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Initializes the adapter state. ```APIDOC ## new(opts) ### Description Initialise adapter state from opts. Returns the value carried in the scope and passed back to every other callback. Called once per connection. ### Signature `new(opts :: keyword()) :: adapter_state()` ``` -------------------------------- ### Initialize and connect with custom configuration Source: https://hexdocs.pm/amarula/Amarula.Config.html Demonstrates how to pass a configuration map to Amarula.new/1 and initiate a connection. ```elixir Amarula.new(%{profile: :me, sync_full_history: false}) |> Amarula.connect() ``` -------------------------------- ### Get Message Specification Source: https://hexdocs.pm/amarula/Amarula.Plugins.MessageStore.html Type specification for retrieving a stored message by its ID. ```elixir @spec get_message(GenServer.server(), String.t()) :: {String.t(), struct()} | nil ``` -------------------------------- ### Implement new callback Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.html Initializes the adapter state from the provided options, called once per connection. ```elixir @callback new(opts :: keyword()) :: adapter_state() ``` -------------------------------- ### span/4 Source: https://hexdocs.pm/amarula/Amarula.Telemetry.html Executes a function as a span, emitting start and stop/exception events with injected metadata. ```APIDOC ## span(name, profile, metadata, fun) ### Description Run the provided function as a span, emitting name ++ [:start] then name ++ [:stop] (or [:exception] if the function raises). The profile is injected into the metadata. The function should return {result, extra_measurements} or {result, extra_measurements, extra_metadata}. ### Signature `span([atom()], profile(), map(), (-> {term(), map()} | {term(), map(), map()})) :: term()` ### Parameters - **name** ([atom()]) - The base name for the span events. - **profile** (profile()) - A connection profile (atom or string). - **metadata** (map()) - Metadata to be associated with the span. - **fun** (function) - The function to execute within the span. ``` -------------------------------- ### Get default adapter Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.html Retrieves the adapter module used when configuration does not specify a message_secret_store. ```elixir @spec default_adapter() :: module() ``` -------------------------------- ### new(config) Source: https://hexdocs.pm/amarula/Amarula.Conn.html Builds a new connection handle from a configuration map. Requires a :profile key to identify the connection. ```APIDOC ## new(config) ### Description Builds a `Conn` from a connection `config` map. The returned `Conn` carries default plugin pipelines. ### Parameters - **config** (map) - Required - A map containing protocol settings, including the required `:profile` key. ``` -------------------------------- ### Execute span Source: https://hexdocs.pm/amarula/Amarula.Telemetry.html Runs a function as a span, emitting start and stop/exception events with injected metadata. ```elixir @spec span([atom()], profile(), map(), (-> {term(), map()} | {term(), map(), map()})) :: term() ``` -------------------------------- ### notify_lid_mappings(pid, pairs) Source: https://hexdocs.pm/amarula/Amarula.Connection.html Cast newly-learned LID↔PN pairs so the consumer gets a :lid_mapping_update event. ```APIDOC ## notify_lid_mappings(pid, pairs) ### Description Cast newly-learned LID↔PN pairs so the consumer gets a :lid_mapping_update event. ### Signature `notify_lid_mappings(GenServer.server(), [{String.t(), String.t()}]) :: :ok` ### Parameters - **pid** (GenServer.server) - The server process identifier. - **pairs** ([{String.t(), String.t()}]) - A list of tuples containing the LID JID and PN JID. ``` -------------------------------- ### Get storage value Source: https://hexdocs.pm/amarula/Amarula.Storage.html Retrieves the value at the specified location, returning :error if missing or corrupted. ```elixir @spec get(Amarula.Storage.Scope.t(), profile(), namespace(), key()) :: {:ok, term()} | :error ``` -------------------------------- ### run(steps, ctx) Source: https://hexdocs.pm/amarula/Amarula.Plugin.html Executes a list of pipeline steps over a given context. ```APIDOC ## run(steps, ctx) ### Description Runs a list of steps over a context map, threading the context through each step sequentially. ### Signature `run([step()], map()) :: {:cont, map()} | {:halt, term()}` ### Parameters - **steps** ([step()]) - A list of pipeline steps to execute. - **ctx** (map()) - The context map containing message data. ``` -------------------------------- ### list_keys(adapter_state, profile, namespace) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Lists all keys stored under a specific profile and namespace. ```APIDOC ## list_keys(adapter_state, profile, namespace) ### Description List every key stored under {profile, namespace}. Order is unspecified. Adapters that don't implement this return {:error, :not_supported}. ### Signature `list_keys(adapter_state(), profile(), namespace()) :: {:ok, [key()]} | {:error, term()}` ``` -------------------------------- ### Get stored entry Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.html Fetches a stored entry by its ID, returning {:ok, entry} or :error. ```elixir @spec get(Amarula.MessageSecretStore.Scope.t(), profile(), String.t()) :: {:ok, entry()} | :error ``` -------------------------------- ### list_profiles(adapter_state) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Lists every profile that has data in the store. ```APIDOC ## list_profiles(adapter_state) ### Description List every profile that has data in this store. Order is unspecified. Adapters that don't implement this return {:error, :not_supported}. ### Signature `list_profiles(adapter_state()) :: {:ok, [profile()]} | {:error, term()}` ``` -------------------------------- ### Install Amarula Dependency Source: https://hexdocs.pm/amarula/index.html Add the Amarula library to your Elixir project's dependency list. ```elixir def deps do [ {:amarula, "~> 0.5"} ] end ``` -------------------------------- ### Implement a custom storage adapter Source: https://hexdocs.pm/amarula/Amarula.Storage.Adapter.html Defines a module using Amarula.Storage.Adapter and implements the required storage callbacks. ```elixir defmodule MyApp.RedisStore do use Amarula.Storage.Adapter @impl true def new(opts), do: %{conn: connect(opts[:url])} @impl true def get(%{conn: c}, namespace, key), do: ... @impl true def put(%{conn: c}, namespace, key, value), do: ... @impl true def delete(%{conn: c}, namespace, key), do: ... end ``` -------------------------------- ### Run pipeline steps Source: https://hexdocs.pm/amarula/Amarula.Plugin.html Specification for executing a list of steps over a context map. ```elixir @spec run([step()], map()) :: {:cont, map()} | {:halt, term()} ``` -------------------------------- ### from_proto(ext) Source: https://hexdocs.pm/amarula/Amarula.Content.LinkPreview.html Builds a %LinkPreview{} struct from an ExtendedTextMessage proto. Returns nil if the message does not contain a valid link preview. ```APIDOC ## from_proto(ext) ### Description Builds a %LinkPreview{} from an ExtendedTextMessage proto (or nil). A preview is considered present when the message has a matched URL, a title, or a description. ### Signature `from_proto(struct() | nil) :: t() | nil` ### Parameters - **ext** (struct() | nil) - The ExtendedTextMessage proto object to parse. ``` -------------------------------- ### Get connection state Source: https://hexdocs.pm/amarula/Amarula.Connection.html Retrieves the %Amarula.Conn{} struct containing the connection's storage scope and profile. ```elixir @spec get_conn(GenServer.server()) :: Amarula.Conn.t() ``` -------------------------------- ### connect/1 Source: https://hexdocs.pm/amarula/Amarula.Connection.html Connects to the WebSocket server. ```APIDOC ## connect(pid) ### Description Connects to the WebSocket server. ``` -------------------------------- ### Clone whatsmeow for behavior cross-checking Source: https://hexdocs.pm/amarula/parity.html Use this command to clone the whatsmeow repository locally for periodic behavioral diffing against Amarula. ```bash git clone --depth 1 https://github.com/tulir/whatsmeow /tmp/whatsmeow ``` -------------------------------- ### Amarula.connect/2 Source: https://hexdocs.pm/amarula/Amarula.html Establishes a connection to WhatsApp and registers a process to receive events. ```APIDOC ## Amarula.connect/2 ### Description Connects to the WhatsApp service. Connection updates and incoming messages are sent to the specified parent process. ### Signature `Amarula.connect(conn, opts)` ### Parameters - **conn** (struct) - Required - The connection handle returned by Amarula.new/1. - **parent_pid** (pid) - Required - The process ID that will receive {:amarula, type, data} messages. ``` -------------------------------- ### Create new connection Source: https://hexdocs.pm/amarula/Amarula.Conn.html Builds a Conn struct from a configuration map. Requires a profile and optionally accepts storage specifications. ```elixir @spec new(map()) :: t() ``` ```elixir Amarula.new(config) |> MyPlugin.attach() |> Amarula.connect() ``` -------------------------------- ### Amarula.RetryCache.ReadOnly Usage Source: https://hexdocs.pm/amarula/Amarula.RetryCache.ReadOnly.html Configuring the Amarula.RetryCache.ReadOnly adapter by providing a :get function to retrieve messages from an existing outbox or database. ```APIDOC ## Usage ### Description Configure the `Amarula.RetryCache.ReadOnly` adapter by passing a `:get` function. This function is invoked by Amarula when a retry is requested, allowing the system to fetch the original message from your existing data store. ### Function Signature `fn profile, msg_id -> {:ok, entry} | :error end` ### Parameters - **profile** (atom) - The profile identifier. - **msg_id** (term) - The unique identifier of the message to retrieve. ### Return Value - **{:ok, entry}** - Where `entry` is a map containing: - `:recipient_jid` (string) - The wire JID the message was originally sent to. - `:message` (%Amarula.Protocol.Proto.Message{}) - The original message object. - **:error** - Returned if the message is not found or no longer available. ### Example ```elixir Amarula.new(%{ profile: :primary, retry_cache: {Amarula.RetryCache.ReadOnly, get: fn _profile, msg_id -> case MyApp.Outbox.fetch(msg_id) do {:ok, %{to: jid, proto: %Amarula.Protocol.Proto.Message{} = m}} -> {:ok, %{recipient_jid: jid, message: m}} :error -> :error end end} }) ``` ``` -------------------------------- ### List keys in a namespace Source: https://hexdocs.pm/amarula/Amarula.Storage.html Retrieves all keys stored under a specific profile and namespace. Returns :not_supported if the adapter does not implement this optional callback. ```elixir @callback list_keys(adapter_state(), profile(), namespace()) :: {:ok, [key()]} | {:error, term()} ``` -------------------------------- ### Get Message Function Specification Source: https://hexdocs.pm/amarula/Amarula.Plugins.MessageStore.html Type specification for the closure used as the socket's :get_message config callback. ```elixir @spec get_message_fun(GenServer.server()) :: (String.t() -> {String.t(), struct()} | nil) ``` -------------------------------- ### from_proto(kind, m) Source: https://hexdocs.pm/amarula/Amarula.Content.Media.html Normalizes a raw media protocol buffer message into an Amarula.Content.Media struct. Since multiple media protos map to this single struct, the kind must be specified explicitly. ```APIDOC ## from_proto(kind, m) ### Description Normalizes a raw media proto (e.g., %Proto.Message.ImageMessage{}) into a %Amarula.Content.Media{} struct. This function snake-cases camelCase fields and surfaces type-relevant metadata. ### Signature `from_proto(kind(), struct()) :: t()` ### Parameters - **kind** (kind()) - Required - The type of media (e.g., :image, :video, :audio, :document, :sticker). - **m** (struct()) - Required - The raw media protocol buffer message. ### Returns - **t()** - A normalized %Amarula.Content.Media{} struct. ``` -------------------------------- ### Persist message secret during upsert Source: https://hexdocs.pm/amarula/Amarula.Msg.html Example of handling message upserts by saving the message secret and sender information to a store. ```elixir def handle_info({:amarula, :messages_upsert, %{messages: msgs}}, state) do for m <- msgs do MyApp.Messages.save(m.id, secret: Amarula.Msg.message_secret(m), sender: m.from) end {:noreply, state} end ``` -------------------------------- ### ensure_local(scope, profile) Source: https://hexdocs.pm/amarula/Amarula.RetryCache.html Initializes local resources for the adapter. ```APIDOC ## ensure_local(scope, profile) ### Description Creates the adapter's process-owned local resource, such as an ETS table. This should be called from the connection initialization. ### Signature `ensure_local(Amarula.RetryCache.Scope.t(), profile()) :: :ok` ``` -------------------------------- ### list_keys(scope, profile, namespace) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Lists every key stored under the specified profile and namespace. ```APIDOC ## list_keys(scope, profile, namespace) ### Description Lists every key stored under the specified profile and namespace. Returns {:error, :not_supported} if the adapter does not support enumeration. ### Signature `list_keys(Amarula.Storage.Scope.t(), profile(), namespace()) :: {:ok, [key()]} | {:error, term()}` ``` -------------------------------- ### from_proto(kind, m) Source: https://hexdocs.pm/amarula/Amarula.Content.Options.html Normalizes one of the four interactive-presentation protocol messages into an Amarula.Content.Options struct. ```APIDOC ## from_proto(kind, m) ### Description Normalizes one of the four interactive-presentation protos into a %Amarula.Content.Options{} struct. The kind parameter specifies which proto type the message m represents. ### Signature from_proto(kind(), struct()) :: t() ### Parameters - **kind** (kind()) - The type of interactive message (:list, :buttons, :template, or :interactive). - **m** (struct()) - The raw protocol message structure to be normalized. ### Returns - **t()** - A normalized %Amarula.Content.Options{} struct containing the unified fields: kind, title, body, footer, button_text, and options. ``` -------------------------------- ### Implement a WhatsApp GenServer Source: https://hexdocs.pm/amarula/index.html Connect to Amarula and handle incoming messages within a GenServer process. ```elixir defmodule MyApp.WhatsApp do use GenServer def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__) @impl true def init(opts) do {:ok, conn} = Amarula.new(%{profile: Keyword.fetch!(opts, :profile)}) |> Amarula.connect(parent_pid: self()) {:ok, %{conn: conn}} end @impl true def handle_info({:amarula, :messages_upsert, %{messages: msgs}}, state) do for %Amarula.Msg{from_me: false, type: :text, content: text, channel: chan} <- msgs do Amarula.send_text(state.conn, chan, "echo: " <> text) end {:noreply, state} end # Everything else (:connection_update, :receipt_update, ...) — take what you need. def handle_info({:amarula, _type, _data}, state), do: {:noreply, state} end ``` -------------------------------- ### Get WhatsApp protocol version Source: https://hexdocs.pm/amarula/Amarula.Config.html Returns the WhatsApp Web protocol version, defaulting to a pinned value or overriding via the AMARULA_WA_VERSION environment variable. ```elixir @spec wa_version() :: [non_neg_integer()] ``` -------------------------------- ### fetch(scope, profile, namespace, key, default) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Fetches the value at the specified location, returning the provided default value if the key is missing. ```APIDOC ## fetch(scope, profile, namespace, key, default) ### Description Fetches the value at the specified location, returning the provided default value if the key is missing. ### Signature `fetch(Amarula.Storage.Scope.t(), profile(), namespace(), key(), term()) :: term()` ``` -------------------------------- ### Configure Amarula with ReadOnly MessageSecretStore Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.ReadOnly.html Initialize Amarula using the ReadOnly adapter by providing a :get function that fetches message details from your application's store. ```elixir Amarula.new(%{ profile: :primary, message_secret_store: {Amarula.MessageSecretStore.ReadOnly, get: fn _profile, msg_id -> case MyApp.Messages.fetch(msg_id) do {:ok, %{message_secret: secret, sender_jid: sender}} when is_binary(secret) -> {:ok, %{secret: secret, sender: sender}} _ -> :error end end} }) ``` -------------------------------- ### Initialize connection with custom retry cache options Source: https://hexdocs.pm/amarula/Amarula.RetryCache.html Configures a specific connection with an ETS retry cache adapter and a custom entry limit. ```elixir Amarula.new(%{profile: :x, retry_cache: {Amarula.RetryCache.ETS, max_entries: 1000}}) ``` -------------------------------- ### scope(scope) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Builds a storage scope from a configuration value, allowing for adapter-specific initialization. ```APIDOC ## scope(scope) ### Description Build a `Amarula.Storage.Scope.t/0` from a `:storage` config value. Accepts a `{adapter, opts}` spec, a bare opts list, or a prebuilt `Scope`. ### Signature `scope(Amarula.Storage.Scope.t() | {module(), keyword()} | keyword()) :: Amarula.Storage.Scope.t()` ### Examples ```elixir Storage.scope({Amarula.Storage.File, root: "./data"}) Storage.scope(root: "./data") # default adapter ``` ``` -------------------------------- ### Configure Amarula with ReadOnly RetryCache Source: https://hexdocs.pm/amarula/Amarula.RetryCache.ReadOnly.html Pass a :get function to the adapter to fetch messages from your existing store by msg_id. The function must return a map containing :recipient_jid and :message, or :error if the message is not found. ```elixir Amarula.new(%{ profile: :primary, retry_cache: {Amarula.RetryCache.ReadOnly, get: fn _profile, msg_id -> case MyApp.Outbox.fetch(msg_id) do {:ok, %{to: jid, proto: %Amarula.Protocol.Proto.Message{} = m}} -> {:ok, %{recipient_jid: jid, message: m}} :error -> :error end end} }) ``` -------------------------------- ### Configure DETS storage per connection Source: https://hexdocs.pm/amarula/Amarula.Storage.DETS.html Initialize a new Amarula connection using the DETS adapter with a specified root directory. ```elixir Amarula.new(%{profile: :primary, storage: {Amarula.Storage.DETS, root: "./data"}}) |> Amarula.connect() ``` -------------------------------- ### on_send(conn, step) Source: https://hexdocs.pm/amarula/Amarula.Plugin.html Appends a step to the send pipeline, which executes over an outgoing message before encryption. ```APIDOC ## on_send(conn, step) ### Description Appends a step to the send pipeline. This step runs over an outgoing message before encryption and can be used to authorize, transform, or drop the message. ### Signature `on_send(Amarula.Conn.t(), step()) :: Amarula.Conn.t()` ### Parameters - **conn** (Amarula.Conn.t()) - The connection to attach the step to. - **step** (step()) - A function `fn ctx -> {:cont, ctx} | {:halt, reason}` to execute. ``` -------------------------------- ### create(conn, subject, participants) Source: https://hexdocs.pm/amarula/Amarula.Group.html Create a new group with a subject and a list of participants. ```APIDOC ## create(conn, subject, participants) ### Description Create a group named `subject` with the given participant jids. Returns the new group's metadata. ### Parameters - **conn** (conn()) - Required - The connection server. - **subject** (String.t()) - Required - The name of the group. - **participants** ([String.t()]) - Required - A list of participant jids. ### Response - **Success** ({:ok, t()}) - Returns the new group metadata. - **Error** ({:error, term()}) - Returns an error if the operation fails. ``` -------------------------------- ### defaults() Source: https://hexdocs.pm/amarula/Amarula.Config.html Retrieves the default configuration map. Note that this map excludes :profile, :auth, and :storage, which must be provided by the caller. ```APIDOC ## defaults() ### Description Returns the default configuration map used by the system. This map does not include caller-supplied fields such as :profile, :auth, or :storage. ### Signature `defaults() :: map()` ``` -------------------------------- ### from_proto(m) Source: https://hexdocs.pm/amarula/Amarula.Content.Location.html Normalizes a %Proto.Message.LocationMessage{} into a %Amarula.Content.Location{} struct. ```APIDOC ## from_proto(m) ### Description Normalizes a `%Proto.Message.LocationMessage{}` into a `%Amarula.Content.Location{}`. ### Signature `from_proto(m :: struct()) :: t()` ### Parameters - **m** (struct) - Required - The protobuf location message to normalize. ``` -------------------------------- ### accept_invite(conn, code) Source: https://hexdocs.pm/amarula/Amarula.Group.html Join a group using an invite code. ```APIDOC ## accept_invite(conn, code) ### Description Join a group by providing an invite code. Returns the joined group's jid on success. ### Parameters - **conn** (conn()) - Required - The connection server. - **code** (String.t()) - Required - The invite code. ### Response - **Success** ({:ok, String.t()}) - Returns the group's jid. - **Error** ({:error, term()}) - Returns an error if the operation fails. ``` -------------------------------- ### List keys in namespace Source: https://hexdocs.pm/amarula/Amarula.Storage.html Lists all keys stored under a specific profile and namespace. Returns :not_supported if enumeration is unavailable. ```elixir @spec list_keys(Amarula.Storage.Scope.t(), profile(), namespace()) :: {:ok, [key()]} | {:error, term()} ``` -------------------------------- ### Run Concurrency Benchmarks Source: https://hexdocs.pm/amarula/custodian_benchmarks.html Execute the provided Elixir scripts to reproduce benchmark results, measure throughput, or analyze memory topology. ```elixir mix run scripts/bench_race_necessity.exs # This document's evidence mix run scripts/bench_flood_receive.exs # Real-crypto throughput/backlog under load mix run scripts/bench_registry_memory.exs # At-rest process/memory topology cost ``` -------------------------------- ### Define namespace type Source: https://hexdocs.pm/amarula/Amarula.Storage.html Lists the valid storage namespaces available in the system. ```elixir @type namespace() :: :creds | :session | :sender_key | :lid_mapping | :device_list | :app_state_sync_key | :app_state_version ``` -------------------------------- ### ensure_local(scope, profile) Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.html Creates the adapter's process-owned local resource. ```APIDOC ## ensure_local(scope, profile) ### Description Creates the adapter's process-owned local resource (e.g., ETS table for the default adapter), owned by the caller. This should be called from `Connection.init`. ### Signature `ensure_local(Amarula.MessageSecretStore.Scope.t(), profile()) :: :ok` ``` -------------------------------- ### Configure default storage and retry cache adapters Source: https://hexdocs.pm/amarula/Amarula.Config.html Sets the global backend modules for storage and retry caching in the application environment. ```elixir config :amarula, :default_storage_adapter, Amarula.Storage.File config :amarula, :retry_cache_adapter, Amarula.RetryCache.ETS ``` -------------------------------- ### Configure Amarula connection Source: https://hexdocs.pm/amarula/index.html Initialize a new Amarula connection with specific profile and storage settings. ```elixir Amarula.new(%{ profile: :me, # required — names + scopes stored state storage: {Amarula.Storage.File, root: "./data"},# storage backend (defaults to File) sync_full_history: false, # skip the full history download max_retries: 5, connect_timeout_ms: 30_000 }) ``` -------------------------------- ### Development Workflow Commands Source: https://hexdocs.pm/amarula/index.html Standard Elixir development commands for managing dependencies, compiling, testing, and linting the project. ```bash mix deps.get # install dependencies mix compile # compile mix test # run the test suite mix format # format mix credo # lint mix dialyzer # type checking ``` -------------------------------- ### wa_version() Source: https://hexdocs.pm/amarula/Amarula.Config.html Returns the WhatsApp Web protocol version currently configured for use. ```APIDOC ## wa_version() ### Description Returns the WhatsApp Web protocol version to present on the wire. It defaults to a pinned version unless the `AMARULA_WA_VERSION` environment variable is set. ### Signature `wa_version() :: [non_neg_integer()]` ``` -------------------------------- ### Configure an Amarula connection with a custom adapter Source: https://hexdocs.pm/amarula/Amarula.Storage.Adapter.html Initializes an Amarula connection using the custom storage adapter module and connection options. ```elixir Amarula.new(%{profile: :p, storage: {MyApp.RedisStore, url: "redis://..."}}) |> Amarula.connect() ``` -------------------------------- ### Ensure local resource creation Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.html Creates the adapter's process-owned local resource, such as an ETS table. Should be called from Connection.init. ```elixir @spec ensure_local(Amarula.MessageSecretStore.Scope.t(), profile()) :: :ok ``` -------------------------------- ### put(adapter_state, profile, namespace, key, value) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Stores a value at the specified location. ```APIDOC ## put(adapter_state, profile, namespace, key, value) ### Description Store value at {profile, namespace, key}, overwriting any prior value. ### Signature `put(adapter_state(), profile(), namespace(), key(), value :: term()) :: :ok | {:error, term()}` ``` -------------------------------- ### Create a PN address Source: https://hexdocs.pm/amarula/Amarula.Address.html Creates a PN address from a bare number or full JID string. ```elixir @spec pn(String.t()) :: t() ``` -------------------------------- ### Amarula.new/1 Source: https://hexdocs.pm/amarula/Amarula.html Initializes a new Amarula connection configuration for a specific profile. ```APIDOC ## Amarula.new/1 ### Description Creates a new connection configuration. The profile name determines the stored credentials used for reconnection. ### Signature `Amarula.new(map())` ### Parameters - **profile** (atom) - Required - The identifier for the account's stored credentials. ``` -------------------------------- ### List profiles Source: https://hexdocs.pm/amarula/Amarula.Storage.html Lists all profiles containing data in the store. Returns :not_supported if enumeration is unavailable. ```elixir @spec list_profiles(Amarula.Storage.Scope.t()) :: {:ok, [profile()]} | {:error, term()} ``` -------------------------------- ### list_profiles(scope) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Lists every profile that contains data in the store. ```APIDOC ## list_profiles(scope) ### Description Lists every profile that contains data in the store. Returns {:error, :not_supported} if the adapter does not support enumeration. ### Signature `list_profiles(Amarula.Storage.Scope.t()) :: {:ok, [profile()]} | {:error, term()}` ``` -------------------------------- ### Build store scope Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.html Constructs a MessageSecretStore.Scope from connection configuration. ```elixir @spec scope(map()) :: Amarula.MessageSecretStore.Scope.t() ``` -------------------------------- ### migrate_pn_sessions(pid, pairs) Source: https://hexdocs.pm/amarula/Amarula.Connection.html Re-key newly-learned LID↔PN pairs so existing PN Signal sessions move onto the LID address instead of renegotiating. ```APIDOC ## migrate_pn_sessions(pid, pairs) ### Description Re-key newly-learned LID↔PN pairs so existing PN Signal sessions move onto the LID address instead of renegotiating. Contacts with no prior PN session get a fresh prekey-bundle fetch instead. ### Signature `migrate_pn_sessions(GenServer.server(), [{String.t(), String.t()}]) :: :ok` ### Parameters - **pid** (GenServer.server) - The server process identifier. - **pairs** ([{String.t(), String.t()}]) - A list of tuples containing the LID JID and PN JID. ``` -------------------------------- ### Build storage scope with scope/1 Source: https://hexdocs.pm/amarula/Amarula.Storage.html Constructs a storage scope from configuration values, supporting adapter specifications or default configurations. ```elixir @spec scope(Amarula.Storage.Scope.t() | {module(), keyword()} | keyword()) :: Amarula.Storage.Scope.t() ``` ```elixir Storage.scope({Amarula.Storage.File, root: "./data"}) Storage.scope(root: "./data") # default adapter ``` -------------------------------- ### Configure Local Git Hooks Source: https://hexdocs.pm/amarula/index.html Enables local pre-commit hooks to ensure code formatting matches CI requirements. ```bash git config core.hooksPath .githooks # runs `mix format --check-formatted` pre-commit ``` -------------------------------- ### whereis(conn_or_config, profile) Source: https://hexdocs.pm/amarula/Amarula.ProfileRegistry.html Locates the live process ID (pid) associated with a specific profile. ```APIDOC ## whereis(conn_or_config, profile) ### Description Retrieves the live Connection pid for the specified profile, or returns `nil` if not found. ### Signature `whereis(Amarula.Conn.t() | map(), term()) :: pid() | nil` ``` -------------------------------- ### Pairing via Mix Task Source: https://hexdocs.pm/amarula/index.html Use the provided Mix task to pair profiles from the shell. ```bash mix amarula.pair my_profile # QR mix amarula.pair my_profile --phone 5511999999999 # phone code ``` -------------------------------- ### Define preview_type() Source: https://hexdocs.pm/amarula/Amarula.Content.LinkPreview.html Defines the possible categories for a link preview rendered by WhatsApp. ```elixir @type preview_type() :: :none | :video | :placeholder | :image | :payment_links | :profile ``` -------------------------------- ### default_adapter() Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.html Returns the default adapter module used when no specific configuration is provided. ```APIDOC ## default_adapter() ### Description Returns the adapter module used when configuration provides bare options or no `:message_secret_store` key. ### Signature `default_adapter() :: module()` ``` -------------------------------- ### Attach callback definition Source: https://hexdocs.pm/amarula/Amarula.Plugin.html Callback signature for attaching plugin steps to a connection. ```elixir @callback attach( Amarula.Conn.t(), keyword() ) :: Amarula.Conn.t() ``` -------------------------------- ### Render QR Code as Image Source: https://hexdocs.pm/amarula/index.html Generate a PNG image from the QR code string for non-terminal environments. ```elixir {:amarula, :connection_update, %{qr: qr}} when is_binary(qr) -> qr |> QRCode.create() |> QRCode.render(:png) |> QRCode.save("qr.png") ``` -------------------------------- ### via(conn_or_config, profile) Source: https://hexdocs.pm/amarula/Amarula.ProfileRegistry.html Generates a :via tuple for a connection's profile, which can be used as a GenServer name. ```APIDOC ## via(conn_or_config, profile) ### Description Returns the `:via` tuple for a connection's profile, used for process registration and lookup. ### Signature `via(Amarula.Conn.t() | map(), term()) :: {:via, module(), {atom(), term()}}` ``` -------------------------------- ### Accept group invite Source: https://hexdocs.pm/amarula/Amarula.Group.html Joins a group using an invite code and returns the group's jid. ```elixir @spec accept_invite(conn(), String.t()) :: {:ok, String.t()} | {:error, term()} ``` -------------------------------- ### send_media(pid, jid, type, data, opts) Source: https://hexdocs.pm/amarula/Amarula.Connection.html Send a media message to a JID. ```APIDOC ## send_media(pid, jid, type, data, opts) ### Description Send a media message to `jid`. `type` is `:image`/`:video`/`:audio`/ `:document`/`:sticker`; `data` is the raw bytes. `opts` may carry `:mimetype` plus per-type extras. Encrypts + uploads + sends. Returns `{:ok, msg_id}` or `{:error, _}`. ``` -------------------------------- ### to_jid(jid) Source: https://hexdocs.pm/amarula/Amarula.Address.html Converts an Address or JID string into the standard WhatsApp JID string format. Returns an error tuple if the address is empty. ```APIDOC ## to_jid(jid) ### Description Converts an `Address` or a JID string into the standard "user@server" string format used by WhatsApp. If a plain string is provided, it is returned unchanged. ### Signature `to_jid(String.t() | t()) :: {:ok, String.t()} | {:error, :no_jid}` ### Parameters - **jid** (String.t() | t()) - Required - The Address struct or JID string to convert. ### Returns - **{:ok, String.t()}** - The formatted JID string. - **{:error, :no_jid}** - Returned if the address is empty (:none). ### Example ```elixir iex> Amarula.Address.to_jid(Amarula.Address.pn("5511999999999")) {:ok, "5511999999999@s.whatsapp.net"} ``` ``` -------------------------------- ### Recompile Protocol Buffers Source: https://hexdocs.pm/amarula/index.html Regenerates Elixir modules from the WhatsApp protocol definition file. ```bash protoc -I proto --elixir_out=package_prefix=amarula.protocol:lib/amarula/protocol/proto wa_proto.proto ``` -------------------------------- ### List profiles in storage Source: https://hexdocs.pm/amarula/Amarula.Storage.html Lists all profiles that have persisted data. This is an optional callback that may return :not_supported. ```elixir @callback list_profiles(adapter_state()) :: {:ok, [profile()]} | {:error, term()} ``` -------------------------------- ### Define from_proto/1 specification Source: https://hexdocs.pm/amarula/Amarula.Content.LinkPreview.html Specifies the function signature for building a LinkPreview struct from a protocol buffer message. ```elixir @spec from_proto(struct() | nil) :: t() | nil ``` -------------------------------- ### deliver_text(conn, opts) Source: https://hexdocs.pm/amarula/Amarula.Testing.html Delivers a plain-text message to the connection, simulating a message received from WhatsApp. ```APIDOC ## deliver_text(conn, opts) ### Description Deliver a plain-text message to the connection, as if WhatsApp sent it. ### Parameters - **conn** (conn()) - Required - The connection process. - **opts** (keyword()) - Required - Options including :from (sender JID), :text (message body), :id (message id), and various stanza attributes like :participant, :recipient, :from_me, and :notify. ``` -------------------------------- ### Create a LID address Source: https://hexdocs.pm/amarula/Amarula.Address.html Creates a LID address from a bare ID or full JID string. ```elixir @spec lid(String.t()) :: t() ``` -------------------------------- ### scope(config) Source: https://hexdocs.pm/amarula/Amarula.MessageSecretStore.html Builds a MessageSecretStore.Scope from a connection configuration. ```APIDOC ## scope(config) ### Description Builds a `MessageSecretStore.Scope` from a connection `config`. It reads the `:message_secret_store` key and falls back to `default_adapter/0` if absent. ### Signature `scope(map()) :: Amarula.MessageSecretStore.Scope.t()` ``` -------------------------------- ### put(scope, profile, namespace, key, value) Source: https://hexdocs.pm/amarula/Amarula.Storage.html Stores a value at the specified profile, namespace, and key location. ```APIDOC ## put(scope, profile, namespace, key, value) ### Description Store `value` at `{profile, namespace, key}`. ### Signature `put(Amarula.Storage.Scope.t(), profile(), namespace(), key(), term()) :: :ok | {:error, term()}` ``` -------------------------------- ### send_message(pid, jid, message) Source: https://hexdocs.pm/amarula/Amarula.Connection.html Send a pre-built message object. ```APIDOC ## send_message(pid, jid, message) ### Description Send a pre-built `%Proto.Message{}` to `jid` (1:1 or group). Used for reactions, edits, deletes and media. Returns `{:ok, msg_id}`. ``` -------------------------------- ### Create a group Source: https://hexdocs.pm/amarula/Amarula.Group.html Creates a new group with a subject and a list of participant jids. ```elixir @spec create(conn(), String.t(), [String.t()]) :: {:ok, t()} | {:error, term()} ``` -------------------------------- ### Configure Global Registry Lease Source: https://hexdocs.pm/amarula/going_prod.html Use this conditional check when deploying in a cluster topology to ensure an external lease is acquired. ```elixir if :global do end ``` -------------------------------- ### Set DETS as default storage adapter Source: https://hexdocs.pm/amarula/Amarula.Storage.DETS.html Configure the global application settings to use DETS as the default storage adapter for all connections. ```elixir config :amarula, default_storage_adapter: Amarula.Storage.DETS ``` -------------------------------- ### send_image(pid, jid, data, opts) Source: https://hexdocs.pm/amarula/Amarula.Connection.html Convenience method to send an image. ```APIDOC ## send_image(pid, jid, data, opts) ### Description Convenience: `send_media(pid, jid, :image, ...)`. ``` -------------------------------- ### from_proto/2 Source: https://hexdocs.pm/amarula/Amarula.Msg.html Builds a %Msg{} structure from a decrypted protocol message and its associated metadata. ```APIDOC ## from_proto(proto, meta) ### Description Builds a %Msg{} from a decrypted proto and its envelope. ### Signature `from_proto(Amarula.Protocol.Proto.Message.t(), map()) :: t()` ### Parameters - **proto** (Amarula.Protocol.Proto.Message.t()) - The decrypted protocol message. - **meta** (map()) - The stanza fields including :id, :channel, :from, :to, :from_me, :pushname, and :timestamp. ```