defmodule BaileysEx do @moduledoc """ Public facade for the BaileysEx connection runtime and major feature surfaces. ## Quick Start alias BaileysEx.Auth.NativeFilePersistence alias BaileysEx.Connection.Transport.MintWebSocket {:ok, persisted_auth} = NativeFilePersistence.use_native_file_auth_state("tmp/baileys_auth") parent = self() {:ok, connection} = BaileysEx.connect( persisted_auth.state, Keyword.merge(persisted_auth.connect_opts, [ transport: {MintWebSocket, []}, on_qr: fn qr -> IO.puts("Scan QR: \#{qr}") end, on_connection: fn update -> IO.inspect({:connection, update}) send(parent, {:connection_update, update}) end ]) ) _creds_unsubscribe = BaileysEx.subscribe_raw(connection, fn events -> if Map.has_key?(events, :creds_update) do {:ok, latest_auth_state} = BaileysEx.auth_state(connection) :ok = persisted_auth.save_creds.(latest_auth_state) end end) receive do {:connection_update, %{connection: :open}} -> :ok after 30_000 -> raise "connection did not open" end unsubscribe = BaileysEx.subscribe(connection, fn {:message, message} -> IO.inspect(message, label: "incoming") {:connection, update} -> IO.inspect(update, label: "connection") _other -> :ok end) unsubscribe.() :ok = BaileysEx.disconnect(connection) ## Persistence Backends Use `BaileysEx.Auth.NativeFilePersistence.use_native_file_auth_state/1` for the recommended durable file-backed setup in Elixir-first apps. Use `BaileysEx.Auth.FilePersistence.use_multi_file_auth_state/1` when you need the Baileys-compatible JSON multi-file auth layout and helper semantics. Custom SQL/NoSQL backends remain supported through `BaileysEx.Auth.Persistence`, `BaileysEx.Auth.KeyStore`, and a matching `signal_store_module`. Advanced custom store modules must implement the explicit transaction-handle contract in `BaileysEx.Signal.Store`; the built-in helpers already wire the correct store for you. Advanced callers can obtain the raw socket transport tuple via `queryable/1` and pass it directly to the lower-level feature modules. Outbound `send_message/4` and `send_status/3` use the built-in production Signal adapter by default when the auth state includes `signed_identity_key`, `signed_pre_key`, and `registration_id`. Use `:signal_repository` or `:signal_repository_adapter` only when you need to override that default. """ alias BaileysEx.Connection.EventEmitter alias BaileysEx.Connection.Store, as: RuntimeStore alias BaileysEx.Connection.Supervisor, as: ConnectionSupervisor alias BaileysEx.Connection.Version, as: ConnectionVersion alias BaileysEx.Feature.Account alias BaileysEx.Feature.AppState alias BaileysEx.Feature.Business alias BaileysEx.Feature.Call alias BaileysEx.Feature.Community alias BaileysEx.Feature.Group alias BaileysEx.Feature.Newsletter alias BaileysEx.Feature.PhoneValidation alias BaileysEx.Feature.Presence alias BaileysEx.Feature.Privacy alias BaileysEx.Feature.Profile alias BaileysEx.JID alias BaileysEx.Media.Download alias BaileysEx.Media.Retry, as: MediaRetry alias BaileysEx.Message.Receipt alias BaileysEx.Protocol.JID, as: JIDUtil alias BaileysEx.Protocol.Proto.WebMessageInfo alias BaileysEx.Signal.Store, as: SignalStore alias BaileysEx.WAM.BinaryInfo, as: WAMBinaryInfo @type connection :: GenServer.server() @type unsubscribe_fun :: (-> :ok) @typedoc """ Opaque WAProto `WebMessageInfo` struct accepted by media download and retry helpers. """ @type web_message_info :: struct() @doc """ Start a connection runtime and optionally attach convenience callbacks. Pass a real `:transport` such as `{BaileysEx.Connection.Transport.MintWebSocket, []}` when you want an actual WhatsApp connection. Without it, the default transport returns `{:error, :transport_not_configured}`. Use `BaileysEx.Auth.NativeFilePersistence.use_native_file_auth_state/1` for the recommended durable built-in file setup. Use `BaileysEx.Auth.FilePersistence.use_multi_file_auth_state/1` when you want the Baileys-compatible JSON multi-file helper instead. Custom persistence backends remain supported through `BaileysEx.Auth.Persistence` plus a compatible `signal_store_module`. Advanced custom store modules must follow the explicit transaction-handle contract in `BaileysEx.Signal.Store`. Supported callback options: - `:on_connection` receives each `connection_update` payload - `:on_qr` receives QR strings extracted from `connection_update` - `:on_message` receives each message from `messages_upsert` - `:on_event` receives the raw buffered event map These convenience callbacks stay attached for the lifetime of the connection runtime. Use `subscribe/2` or `subscribe_raw/2` when you need an explicit unsubscribe handle. """ @spec connect(term(), keyword()) :: {:ok, pid()} | {:error, term()} def connect(auth_state, opts \\ []) when is_list(opts) do {callback_opts, connection_opts} = Keyword.split(opts, [:on_connection, :on_event, :on_message, :on_qr]) auth_state |> ConnectionSupervisor.start_connection( maybe_put_initial_event_subscribers(connection_opts, callback_opts) ) end @doc "Stop a running connection runtime." @spec disconnect(connection()) :: :ok | {:error, term()} def disconnect(connection), do: ConnectionSupervisor.stop_connection(connection) @doc "Return the underlying socket transport tuple used by feature modules." @spec queryable(connection()) :: {:ok, {module(), pid()}} | {:error, :socket_not_available} def queryable(connection) do case ConnectionSupervisor.queryable(connection) do {module, pid} -> {:ok, {module, pid}} nil -> {:error, :socket_not_available} end end @doc "Return the connection event emitter pid." @spec event_emitter(connection()) :: {:ok, pid()} | {:error, :event_emitter_not_available} def event_emitter(connection) do case ConnectionSupervisor.event_emitter(connection) do pid when is_pid(pid) -> {:ok, pid} _ -> {:error, :event_emitter_not_available} end end @doc "Return the wrapped Signal store for the connection when present." @spec signal_store(connection()) :: {:ok, SignalStore.t()} | {:error, :signal_store_not_available} def signal_store(connection) do case ConnectionSupervisor.signal_store(connection) do %SignalStore{} = store -> {:ok, store} _ -> {:error, :signal_store_not_available} end end @doc """ Return the current auth state snapshot from the connection store. `creds_update` events are mirrored into the store before public subscribers run, so callers can safely read `auth_state/1` from inside those callbacks. """ @spec auth_state(connection()) :: {:ok, map()} | {:error, :store_not_available} def auth_state(connection) do case ConnectionSupervisor.store(connection) do pid when is_pid(pid) -> store_ref = BaileysEx.Connection.Store.wrap(pid) {:ok, BaileysEx.Connection.Store.get(store_ref, :auth_state, %{})} _ -> {:error, :store_not_available} end end @doc "Subscribe to raw buffered event maps." @spec subscribe_raw(connection(), (map() -> term())) :: unsubscribe_fun() | {:error, :event_emitter_not_available} def subscribe_raw(connection, handler) when is_function(handler, 1) do ConnectionSupervisor.subscribe(connection, handler) end @doc """ Subscribe to friendly high-level events. The handler receives: - `{:connection, update}` for connection lifecycle updates - `{:message, message}` for each incoming message - `{:presence, update}` for presence updates - `{:call, payload}` for call events - `{:event, name, payload}` for all other event payloads """ @spec subscribe(connection(), (tuple() -> term())) :: unsubscribe_fun() | {:error, :event_emitter_not_available} def subscribe(connection, handler) when is_function(handler, 1) do subscribe_raw(connection, fn events -> dispatch_public_events(events, handler) end) end @doc "Request a phone-number pairing code from the active socket." @spec request_pairing_code(connection(), binary(), keyword()) :: {:ok, binary()} | {:error, term()} def request_pairing_code(connection, phone_number, opts \\ []) when is_binary(phone_number) and is_list(opts) do ConnectionSupervisor.request_pairing_code(connection, phone_number, opts) end @doc """ Send a message to a WhatsApp JID through the coordinator-managed runtime. By default, `connect/2` builds the production Signal adapter when the auth state includes `signed_identity_key`, `signed_pre_key`, and `registration_id`. Use `:signal_repository` or `:signal_repository_adapter` only to override it. """ @spec send_message(connection(), String.t() | JID.t(), map() | struct(), keyword()) :: {:ok, map()} | {:error, term()} def send_message(connection, jid, content, opts \\ []) when is_list(opts) do with {:ok, parsed_jid} <- normalize_jid(jid) do ConnectionSupervisor.send_message(connection, parsed_jid, content, opts) end end @doc """ Send a status update through the `status@broadcast` fanout path. By default, `connect/2` builds the production Signal adapter when the auth state includes `signed_identity_key`, `signed_pre_key`, and `registration_id`. Use `:signal_repository` or `:signal_repository_adapter` only to override it. """ @spec send_status(connection(), map(), keyword()) :: {:ok, map()} | {:error, term()} def send_status(connection, content, opts \\ []) when is_map(content) and is_list(opts) do ConnectionSupervisor.send_status(connection, content, opts) end @doc "Send a WAM analytics buffer or `BaileysEx.WAM.BinaryInfo` through the active socket." @spec send_wam_buffer(connection(), binary() | WAMBinaryInfo.t()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def send_wam_buffer(connection, %WAMBinaryInfo{} = binary_info) do binary_info |> BaileysEx.WAM.encode() |> then(&ConnectionSupervisor.send_wam_buffer(connection, &1)) end def send_wam_buffer(connection, wam_buffer) when is_binary(wam_buffer) do ConnectionSupervisor.send_wam_buffer(connection, wam_buffer) end @doc "Fetch the latest version published by Baileys' current `Defaults/index.ts`." @spec fetch_latest_baileys_version(keyword()) :: map() def fetch_latest_baileys_version(opts \\ []), do: ConnectionVersion.fetch_latest_baileys_version(opts) @doc "Fetch the latest WhatsApp Web client revision from `web.whatsapp.com/sw.js`." @spec fetch_latest_wa_web_version(keyword()) :: map() def fetch_latest_wa_web_version(opts \\ []), do: ConnectionVersion.fetch_latest_wa_web_version(opts) @doc "Fetch the current account reachout timelock restriction state." @spec fetch_account_reachout_timelock(connection(), keyword()) :: {:ok, map()} | {:error, term()} def fetch_account_reachout_timelock(connection, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Account.fetch_account_reachout_timelock(queryable, runtime_opts) end) end @doc "Fetch the current account new-chat message cap quota and usage." @spec fetch_new_chat_message_cap(connection(), keyword()) :: {:ok, map()} | {:error, term()} def fetch_new_chat_message_cap(connection, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Account.fetch_new_chat_message_cap(queryable, runtime_opts) end) end @doc "Send an availability or chatstate update." @spec send_presence_update(connection(), Presence.presence(), String.t() | nil, keyword()) :: :ok | {:error, term()} def send_presence_update(connection, type, to_jid \\ nil, opts \\ []) when is_list(opts) do with_queryable(connection, fn queryable -> Presence.send_update(queryable, type, to_jid, maybe_put_presence_identity(opts, connection)) end) end @doc "Subscribe to a contact or group presence feed." @spec presence_subscribe(connection(), String.t(), keyword()) :: :ok | {:error, term()} def presence_subscribe(connection, jid, opts \\ []) when is_binary(jid) and is_list(opts) do with_queryable(connection, fn queryable -> Presence.subscribe(queryable, jid, maybe_put_signal_store(opts, connection)) end) end @doc "Archive or unarchive a chat via the app-state patch path." @spec archive_chat(connection(), String.t(), boolean(), list(), keyword()) :: :ok | {:error, term()} def archive_chat(connection, jid, archive?, last_messages, opts \\ []) when is_binary(jid) and is_list(last_messages) and is_list(opts) do with_app_state_runtime( connection, :archive, jid, %{archive: archive?, last_messages: last_messages}, opts ) end @doc "Mute a chat until the given Unix timestamp, or pass `nil` to unmute." @spec mute_chat(connection(), String.t(), integer() | nil, keyword()) :: :ok | {:error, term()} def mute_chat(connection, jid, duration, opts \\ []) when is_binary(jid) and is_list(opts) do with_app_state_runtime(connection, :mute, jid, duration, opts) end @doc "Pin or unpin a chat." @spec pin_chat(connection(), String.t(), boolean(), keyword()) :: :ok | {:error, term()} def pin_chat(connection, jid, pin?, opts \\ []) when is_binary(jid) and is_list(opts) do with_app_state_runtime(connection, :pin, jid, pin?, opts) end @doc "Star or unstar one or more messages in a chat." @spec star_messages(connection(), String.t(), [map()], boolean(), keyword()) :: :ok | {:error, term()} def star_messages(connection, jid, messages, star?, opts \\ []) when is_binary(jid) and is_list(messages) and is_list(opts) do with_app_state_runtime(connection, :star, jid, %{messages: messages, star: star?}, opts) end @doc "Mark a chat read or unread." @spec mark_chat_read(connection(), String.t(), boolean(), list(), keyword()) :: :ok | {:error, term()} def mark_chat_read(connection, jid, read?, last_messages, opts \\ []) when is_binary(jid) and is_list(last_messages) and is_list(opts) do with_app_state_runtime( connection, :mark_read, jid, %{read: read?, last_messages: last_messages}, opts ) end @doc "Clear a chat history via app-state sync." @spec clear_chat(connection(), String.t(), list(), keyword()) :: :ok | {:error, term()} def clear_chat(connection, jid, last_messages, opts \\ []) when is_binary(jid) and is_list(last_messages) and is_list(opts) do with_app_state_runtime(connection, :clear, jid, %{last_messages: last_messages}, opts) end @doc "Delete a chat via app-state sync." @spec delete_chat(connection(), String.t(), list(), keyword()) :: :ok | {:error, term()} def delete_chat(connection, jid, last_messages, opts \\ []) when is_binary(jid) and is_list(last_messages) and is_list(opts) do with_app_state_runtime(connection, :delete, jid, %{last_messages: last_messages}, opts) end @doc "Delete a specific message for the current device." @spec delete_message_for_me(connection(), String.t(), map(), integer(), boolean(), keyword()) :: :ok | {:error, term()} def delete_message_for_me( connection, jid, message_key, timestamp, delete_media? \\ false, opts \\ [] ) when is_binary(jid) and is_map(message_key) and is_integer(timestamp) and is_list(opts) do with_app_state_runtime( connection, :delete_for_me, jid, %{key: message_key, timestamp: timestamp, delete_media: delete_media?}, opts ) end @doc "Send read receipts for the provided message keys using current privacy settings." @spec read_messages(connection(), [map()], keyword()) :: :ok | {:error, term()} def read_messages(connection, keys, opts \\ []) when is_list(keys) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> with {:ok, settings} <- Privacy.fetch_settings(queryable, false, runtime_opts) do Receipt.read_messages( runtime_send_node_fun(queryable), keys, normalize_privacy_settings(settings), opts ) end end) end @doc "Update the app-state privacy toggle that disables server-side link previews." @spec update_link_previews_privacy(connection(), boolean(), keyword()) :: :ok | {:error, term()} def update_link_previews_privacy(connection, disabled?, opts \\ []) when is_list(opts) do with_app_state_runtime(connection, :disable_link_previews, "", disabled?, opts) end @doc "Check which phone numbers are registered on WhatsApp." @spec on_whatsapp(connection(), [String.t()], keyword()) :: {:ok, [%{exists: boolean(), jid: String.t()}]} | {:error, term()} def on_whatsapp(connection, phone_numbers, opts \\ []) when is_list(phone_numbers) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> PhoneValidation.on_whatsapp(queryable, phone_numbers, runtime_opts) end) end @doc "Download media into memory." @spec download_media(map(), keyword()) :: {:ok, binary()} | {:error, term()} def download_media(message, opts \\ []), do: Download.download(message, opts) @doc "Download media directly to a file path." @spec download_media_to_file(map(), Path.t(), keyword()) :: {:ok, Path.t()} | {:error, term()} def download_media_to_file(message, path, opts \\ []), do: Download.download_to_file(message, path, opts) @doc """ Request media re-upload and return the updated message with refreshed URLs. Sends a retry request, waits for the matching `messages_media_update` event, decrypts the response, and applies the refreshed `directPath`/`url`. ## Options * `:timeout` — milliseconds to wait for the media update event (default 10_000) """ @spec update_media_message(connection(), web_message_info(), keyword()) :: {:ok, web_message_info()} | {:error, term()} def update_media_message(connection, %WebMessageInfo{} = message, opts \\ []) when is_list(opts) do with {:ok, queryable} <- queryable(connection), {:ok, emitter} <- event_emitter(connection), {:ok, auth} <- auth_state(connection) do me_id = get_in(auth, [:me, :id]) || get_in(auth, [:me_id]) if is_binary(me_id) do {_mod, socket} = queryable MediaRetry.update_media_message( socket, emitter, message, Keyword.put(opts, :me_id, me_id) ) else {:error, :me_id_not_available} end end end @doc "Fetch a profile picture URL." @spec profile_picture_url(connection(), String.t(), Profile.picture_type(), keyword()) :: {:ok, String.t() | nil} | {:error, term()} def profile_picture_url(connection, jid, type \\ :preview, opts \\ []) when is_binary(jid) and is_list(opts) do with_queryable(connection, fn queryable -> Profile.picture_url(queryable, jid, type, maybe_put_signal_store(opts, connection)) end) end @doc "Update the account status text." @spec update_profile_status(connection(), String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_profile_status(connection, status, opts \\ []) when is_binary(status) and is_list(opts) do with_queryable(connection, fn queryable -> Profile.update_status(queryable, status, opts) end) end @doc "Fetch profile status text for one or more JIDs." @spec fetch_status(connection(), [String.t()], keyword()) :: {:ok, [map()]} | {:error, term()} def fetch_status(connection, jids, opts \\ []) when is_list(jids) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Profile.fetch_status(queryable, jids, runtime_opts) end) end @doc "Fetch the business profile for a WhatsApp JID." @spec business_profile(connection(), String.t(), keyword()) :: {:ok, map() | nil} | {:error, term()} def business_profile(connection, jid, opts \\ []) when is_binary(jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Profile.get_business_profile(queryable, jid, runtime_opts) end) end @doc "Update the current account push name via app-state sync." @spec update_profile_name(connection(), String.t(), keyword()) :: :ok | {:error, term()} def update_profile_name(connection, name, opts \\ []) when is_binary(name) and is_list(opts) do with_app_state_runtime(connection, :push_name_setting, "", name, opts) end @doc "Update the profile picture for yourself or a group." @spec update_profile_picture(connection(), String.t(), term(), map() | nil, keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_profile_picture(connection, jid, image_data, dimensions \\ nil, opts \\ []) when is_binary(jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Profile.update_picture(queryable, jid, image_data, dimensions, runtime_opts) end) end @doc "Remove the profile picture for yourself or a group." @spec remove_profile_picture(connection(), String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def remove_profile_picture(connection, jid, opts \\ []) when is_binary(jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Profile.remove_picture(queryable, jid, runtime_opts) end) end @doc "Reject an inbound call." @spec reject_call(connection(), String.t(), String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def reject_call(connection, call_id, caller_jid, opts \\ []) when is_binary(call_id) and is_binary(caller_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Call.reject_call(queryable, call_id, caller_jid, runtime_opts) end) end @doc "Create an audio or video call link." @spec create_call_link(connection(), :audio | :video, map() | nil, keyword()) :: {:ok, String.t() | nil} | {:error, term()} def create_call_link(connection, type, event \\ nil, opts \\ []) when type in [:audio, :video] and (is_map(event) or is_nil(event)) and is_list(opts) do merged_opts = if is_nil(event) do opts else Keyword.put_new(opts, :event, event) end with_runtime(connection, merged_opts, fn queryable, runtime_opts -> Call.create_call_link(queryable, type, runtime_opts) end) end @doc "Create a group." @spec group_create(connection(), String.t(), [String.t()], keyword()) :: {:ok, map()} | {:error, term()} def group_create(connection, subject, participants, opts \\ []) when is_binary(subject) and is_list(participants) and is_list(opts) do with_queryable(connection, fn queryable -> Group.create(queryable, subject, participants, opts) end) end @doc "Fetch group metadata." @spec group_metadata(connection(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def group_metadata(connection, group_jid, opts \\ []) when is_binary(group_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Group.get_metadata(queryable, group_jid, opts) end) end @doc "Leave a group." @spec group_leave(connection(), String.t()) :: :ok | {:error, term()} def group_leave(connection, group_jid) when is_binary(group_jid) do with_queryable(connection, fn queryable -> Group.leave(queryable, group_jid) end) end @doc "Add, remove, promote, or demote group participants." @spec group_participants_update( connection(), String.t(), [String.t()], :add | :remove | :promote | :demote, keyword() ) :: {:ok, [map()]} | {:error, term()} def group_participants_update(connection, group_jid, jids, action, opts \\ []) when is_binary(group_jid) and is_list(jids) and action in [:add, :remove, :promote, :demote] and is_list(opts) do with_queryable(connection, fn queryable -> case action do :add -> Group.add_participants(queryable, group_jid, jids) :remove -> Group.remove_participants(queryable, group_jid, jids) :promote -> Group.promote_participants(queryable, group_jid, jids) :demote -> Group.demote_participants(queryable, group_jid, jids) end end) end @doc "Update a group subject." @spec group_update_subject(connection(), String.t(), String.t(), keyword()) :: :ok | {:error, term()} def group_update_subject(connection, group_jid, subject, opts \\ []) when is_binary(group_jid) and is_binary(subject) and is_list(opts) do with_queryable(connection, fn queryable -> Group.update_subject(queryable, group_jid, subject) end) end @doc "Update or clear a group description." @spec group_update_description(connection(), String.t(), String.t() | nil, keyword()) :: :ok | {:error, term()} def group_update_description(connection, group_jid, description, opts \\ []) when is_binary(group_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Group.update_description(queryable, group_jid, description, opts) end) end @doc "Update a group announcement or locked setting." @spec group_setting_update( connection(), String.t(), :announcement | :not_announcement | :locked | :unlocked, keyword() ) :: :ok | {:error, term()} def group_setting_update(connection, group_jid, setting, opts \\ []) when is_binary(group_jid) and setting in [:announcement, :not_announcement, :locked, :unlocked] and is_list(opts) do with_queryable(connection, fn queryable -> Group.setting_update(queryable, group_jid, setting) end) end @doc "Fetch the current group invite code." @spec group_invite_code(connection(), String.t(), keyword()) :: {:ok, String.t() | nil} | {:error, term()} def group_invite_code(connection, group_jid, opts \\ []) when is_binary(group_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Group.invite_code(queryable, group_jid) end) end @doc "Revoke the current group invite code and return the new one." @spec group_revoke_invite(connection(), String.t(), keyword()) :: {:ok, String.t() | nil} | {:error, term()} def group_revoke_invite(connection, group_jid, opts \\ []) when is_binary(group_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Group.revoke_invite(queryable, group_jid) end) end @doc "Accept a legacy group invite code." @spec group_accept_invite(connection(), String.t(), keyword()) :: {:ok, String.t() | nil} | {:error, term()} def group_accept_invite(connection, code, opts \\ []) when is_binary(code) and is_list(opts) do with_queryable(connection, fn queryable -> Group.accept_invite(queryable, code) end) end @doc "Fetch group invite metadata without joining." @spec group_get_invite_info(connection(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def group_get_invite_info(connection, code, opts \\ []) when is_binary(code) and is_list(opts) do with_queryable(connection, fn queryable -> Group.get_invite_info(queryable, code) end) end @doc "Accept a v4 group invite." @spec group_accept_invite_v4(connection(), String.t() | map(), map(), keyword()) :: {:ok, String.t() | nil} | {:error, term()} def group_accept_invite_v4(connection, key, invite_message, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Group.accept_invite_v4(queryable, key, invite_message, runtime_opts) end) end @doc "Revoke a v4 group invite for a specific invited user." @spec group_revoke_invite_v4(connection(), String.t(), String.t(), keyword()) :: :ok | {:error, term()} def group_revoke_invite_v4(connection, group_jid, invited_jid, opts \\ []) when is_binary(group_jid) and is_binary(invited_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Group.revoke_invite_v4(queryable, group_jid, invited_jid) end) end @doc "Fetch the pending join-request list for a group." @spec group_request_participants_list(connection(), String.t(), keyword()) :: {:ok, [map()]} | {:error, term()} def group_request_participants_list(connection, group_jid, opts \\ []) when is_binary(group_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Group.request_participants_list(queryable, group_jid) end) end @doc "Approve or reject pending group join requests." @spec group_request_participants_update( connection(), String.t(), [String.t()], :approve | :reject, keyword() ) :: {:ok, [map()]} | {:error, term()} def group_request_participants_update(connection, group_jid, jids, action, opts \\ []) when is_binary(group_jid) and is_list(jids) and action in [:approve, :reject] and is_list(opts) do with_queryable(connection, fn queryable -> Group.request_participants_update(queryable, group_jid, jids, action) end) end @doc "Fetch all participating groups." @spec group_fetch_all_participating(connection(), keyword()) :: {:ok, %{String.t() => map()}} | {:error, term()} def group_fetch_all_participating(connection, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Group.fetch_all_participating(queryable, runtime_opts) end) end @doc "Enable or disable disappearing messages for a group." @spec group_toggle_ephemeral(connection(), String.t(), non_neg_integer(), keyword()) :: :ok | {:error, term()} def group_toggle_ephemeral(connection, group_jid, expiration, opts \\ []) when is_binary(group_jid) and is_integer(expiration) and expiration >= 0 and is_list(opts) do with_queryable(connection, fn queryable -> Group.toggle_ephemeral(queryable, group_jid, expiration) end) end @doc "Switch whether only admins or all members can add participants." @spec group_member_add_mode(connection(), String.t(), :admin_add | :all_member_add, keyword()) :: :ok | {:error, term()} def group_member_add_mode(connection, group_jid, mode, opts \\ []) when is_binary(group_jid) and mode in [:admin_add, :all_member_add] and is_list(opts) do with_queryable(connection, fn queryable -> Group.member_add_mode(queryable, group_jid, mode) end) end @doc "Toggle join-approval mode for a group." @spec group_join_approval_mode(connection(), String.t(), :on | :off, keyword()) :: :ok | {:error, term()} def group_join_approval_mode(connection, group_jid, mode, opts \\ []) when is_binary(group_jid) and mode in [:on, :off] and is_list(opts) do with_queryable(connection, fn queryable -> Group.join_approval_mode(queryable, group_jid, mode) end) end @doc "Create a community." @spec community_create(connection(), String.t(), String.t() | nil, keyword()) :: {:ok, map()} | {:error, term()} def community_create(connection, subject, description \\ nil, opts \\ []) when is_binary(subject) and (is_binary(description) or is_nil(description)) and is_list(opts) do with_queryable(connection, fn queryable -> Community.create(queryable, subject, description, opts) end) end @doc "Fetch community metadata." @spec community_metadata(connection(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def community_metadata(connection, community_jid, opts \\ []) when is_binary(community_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.metadata(queryable, community_jid, opts) end) end @doc "Create a linked subgroup inside a community." @spec community_create_group(connection(), String.t(), [String.t()], String.t(), keyword()) :: {:ok, map() | nil} | {:error, term()} def community_create_group(connection, subject, participants, parent_community_jid, opts \\ []) when is_binary(subject) and is_list(participants) and is_binary(parent_community_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.create_group(queryable, subject, participants, parent_community_jid, opts) end) end @doc "Leave a community." @spec community_leave(connection(), String.t(), keyword()) :: :ok | {:error, term()} def community_leave(connection, community_jid, opts \\ []) when is_binary(community_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.leave(queryable, community_jid) end) end @doc "Update a community subject." @spec community_update_subject(connection(), String.t(), String.t(), keyword()) :: :ok | {:error, term()} def community_update_subject(connection, community_jid, subject, opts \\ []) when is_binary(community_jid) and is_binary(subject) and is_list(opts) do with_queryable(connection, fn queryable -> Community.update_subject(queryable, community_jid, subject) end) end @doc "Update or clear a community description." @spec community_update_description(connection(), String.t(), String.t() | nil, keyword()) :: :ok | {:error, term()} def community_update_description(connection, community_jid, description, opts \\ []) when is_binary(community_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.update_description(queryable, community_jid, description, opts) end) end @doc "Link an existing subgroup into a community." @spec community_link_group(connection(), String.t(), String.t(), keyword()) :: :ok | {:error, term()} def community_link_group(connection, group_jid, parent_community_jid, opts \\ []) when is_binary(group_jid) and is_binary(parent_community_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.link_group(queryable, group_jid, parent_community_jid) end) end @doc "Unlink a subgroup from a community." @spec community_unlink_group(connection(), String.t(), String.t(), keyword()) :: :ok | {:error, term()} def community_unlink_group(connection, group_jid, parent_community_jid, opts \\ []) when is_binary(group_jid) and is_binary(parent_community_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.unlink_group(queryable, group_jid, parent_community_jid) end) end @doc "Fetch linked groups for a community or subgroup." @spec community_fetch_linked_groups(connection(), String.t(), keyword()) :: {:ok, %{community_jid: String.t(), is_community: boolean(), linked_groups: [map()]}} | {:error, term()} def community_fetch_linked_groups(connection, jid, opts \\ []) when is_binary(jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.fetch_linked_groups(queryable, jid) end) end @doc "Add, remove, promote, or demote community participants." @spec community_participants_update( connection(), String.t(), [String.t()], :add | :remove | :promote | :demote, keyword() ) :: {:ok, [map()]} | {:error, term()} def community_participants_update(connection, community_jid, jids, action, opts \\ []) when is_binary(community_jid) and is_list(jids) and action in [:add, :remove, :promote, :demote] and is_list(opts) do with_queryable(connection, fn queryable -> Community.participants_update(queryable, community_jid, jids, action) end) end @doc "Fetch the pending membership-approval request list for a community." @spec community_request_participants_list(connection(), String.t(), keyword()) :: {:ok, [map()]} | {:error, term()} def community_request_participants_list(connection, community_jid, opts \\ []) when is_binary(community_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.request_participants_list(queryable, community_jid) end) end @doc "Approve or reject pending community membership requests." @spec community_request_participants_update( connection(), String.t(), [String.t()], :approve | :reject, keyword() ) :: {:ok, [map()]} | {:error, term()} def community_request_participants_update(connection, community_jid, jids, action, opts \\ []) when is_binary(community_jid) and is_list(jids) and action in [:approve, :reject] and is_list(opts) do with_queryable(connection, fn queryable -> Community.request_participants_update(queryable, community_jid, jids, action) end) end @doc "Fetch the current community invite code." @spec community_invite_code(connection(), String.t(), keyword()) :: {:ok, String.t() | nil} | {:error, term()} def community_invite_code(connection, community_jid, opts \\ []) when is_binary(community_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.invite_code(queryable, community_jid) end) end @doc "Revoke the current community invite code and return the new one." @spec community_revoke_invite(connection(), String.t(), keyword()) :: {:ok, String.t() | nil} | {:error, term()} def community_revoke_invite(connection, community_jid, opts \\ []) when is_binary(community_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.revoke_invite(queryable, community_jid) end) end @doc "Accept a legacy community invite code." @spec community_accept_invite(connection(), String.t(), keyword()) :: {:ok, String.t() | nil} | {:error, term()} def community_accept_invite(connection, code, opts \\ []) when is_binary(code) and is_list(opts) do with_queryable(connection, fn queryable -> Community.accept_invite(queryable, code) end) end @doc "Fetch community invite metadata without joining." @spec community_get_invite_info(connection(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def community_get_invite_info(connection, code, opts \\ []) when is_binary(code) and is_list(opts) do with_queryable(connection, fn queryable -> Community.get_invite_info(queryable, code) end) end @doc "Accept a v4 community invite." @spec community_accept_invite_v4(connection(), String.t() | map(), map(), keyword()) :: {:ok, String.t() | nil} | {:error, term()} def community_accept_invite_v4(connection, key, invite_message, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Community.accept_invite_v4(queryable, key, invite_message, runtime_opts) end) end @doc "Revoke a v4 community invite for a specific invited user." @spec community_revoke_invite_v4(connection(), String.t(), String.t(), keyword()) :: :ok | {:error, term()} def community_revoke_invite_v4(connection, community_jid, invited_jid, opts \\ []) when is_binary(community_jid) and is_binary(invited_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Community.revoke_invite_v4(queryable, community_jid, invited_jid) end) end @doc "Enable or disable disappearing messages for a community." @spec community_toggle_ephemeral(connection(), String.t(), non_neg_integer(), keyword()) :: :ok | {:error, term()} def community_toggle_ephemeral(connection, community_jid, expiration, opts \\ []) when is_binary(community_jid) and is_integer(expiration) and expiration >= 0 and is_list(opts) do with_queryable(connection, fn queryable -> Community.toggle_ephemeral(queryable, community_jid, expiration) end) end @doc "Update a community announcement or locked setting." @spec community_setting_update( connection(), String.t(), :announcement | :not_announcement | :locked | :unlocked, keyword() ) :: :ok | {:error, term()} def community_setting_update(connection, community_jid, setting, opts \\ []) when is_binary(community_jid) and setting in [:announcement, :not_announcement, :locked, :unlocked] and is_list(opts) do with_queryable(connection, fn queryable -> Community.setting_update(queryable, community_jid, setting) end) end @doc "Switch whether only admins or all members can add participants to a community." @spec community_member_add_mode( connection(), String.t(), :admin_add | :all_member_add, keyword() ) :: :ok | {:error, term()} def community_member_add_mode(connection, community_jid, mode, opts \\ []) when is_binary(community_jid) and mode in [:admin_add, :all_member_add] and is_list(opts) do with_queryable(connection, fn queryable -> Community.member_add_mode(queryable, community_jid, mode) end) end @doc "Toggle join-approval mode for a community." @spec community_join_approval_mode(connection(), String.t(), :on | :off, keyword()) :: :ok | {:error, term()} def community_join_approval_mode(connection, community_jid, mode, opts \\ []) when is_binary(community_jid) and mode in [:on, :off] and is_list(opts) do with_queryable(connection, fn queryable -> Community.join_approval_mode(queryable, community_jid, mode) end) end @doc "Fetch all participating communities." @spec community_fetch_all_participating(connection(), keyword()) :: {:ok, %{String.t() => map()}} | {:error, term()} def community_fetch_all_participating(connection, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Community.fetch_all_participating(queryable, runtime_opts) end) end @doc "Fetch privacy settings." @spec privacy_settings(connection(), keyword()) :: {:ok, map()} | {:error, term()} def privacy_settings(connection, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.fetch_settings(queryable, true, runtime_opts) end) end @doc "Fetch the account blocklist." @spec fetch_blocklist(connection(), keyword()) :: {:ok, [String.t()]} | {:error, term()} def fetch_blocklist(connection, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.fetch_blocklist(queryable, runtime_opts) end) end @doc "Block or unblock a user." @spec update_block_status(connection(), String.t(), :block | :unblock, keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_block_status(connection, jid, action, opts \\ []) when is_binary(jid) and action in [:block, :unblock] and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.update_block_status(queryable, jid, action, runtime_opts) end) end @doc "Update last-seen privacy." @spec update_last_seen_privacy(connection(), atom() | String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_last_seen_privacy(connection, value, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.update_last_seen(queryable, value, runtime_opts) end) end @doc "Update online-visibility privacy." @spec update_online_privacy(connection(), atom() | String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_online_privacy(connection, value, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.update_online(queryable, value, runtime_opts) end) end @doc "Update profile-picture privacy." @spec update_profile_picture_privacy(connection(), atom() | String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_profile_picture_privacy(connection, value, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.update_profile_picture(queryable, value, runtime_opts) end) end @doc "Update status/story privacy." @spec update_status_privacy(connection(), atom() | String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_status_privacy(connection, value, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.update_status(queryable, value, runtime_opts) end) end @doc "Update read-receipt privacy." @spec update_read_receipts_privacy(connection(), atom() | String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_read_receipts_privacy(connection, value, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.update_read_receipts(queryable, value, runtime_opts) end) end @doc "Update group-add privacy." @spec update_groups_add_privacy(connection(), atom() | String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_groups_add_privacy(connection, value, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.update_group_add(queryable, value, runtime_opts) end) end @doc "Set the default disappearing-message duration in seconds." @spec update_default_disappearing_mode(connection(), non_neg_integer(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_default_disappearing_mode(connection, duration, opts \\ []) when is_integer(duration) and duration >= 0 and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.update_default_disappearing_mode(queryable, duration, runtime_opts) end) end @doc "Update call-add privacy." @spec update_call_privacy(connection(), atom() | String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_call_privacy(connection, value, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.update_call_add(queryable, value, runtime_opts) end) end @doc "Update who can message you." @spec update_messages_privacy(connection(), atom() | String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_messages_privacy(connection, value, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Privacy.update_messages(queryable, value, runtime_opts) end) end @doc "Fetch a business catalog." @spec business_catalog(connection(), keyword()) :: {:ok, map()} | {:error, term()} def business_catalog(connection, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Business.get_catalog(queryable, runtime_opts) end) end @doc "Update the business profile payload." @spec update_business_profile(connection(), map(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def update_business_profile(connection, profile_data, opts \\ []) when is_map(profile_data) and is_list(opts) do with_queryable(connection, fn queryable -> Business.update_business_profile(queryable, profile_data, opts) end) end @doc "Upload and set the business cover photo." @spec update_business_cover_photo(connection(), term(), keyword()) :: {:ok, String.t()} | {:error, term()} def update_business_cover_photo(connection, photo, opts \\ []) when is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Business.update_cover_photo(queryable, photo, runtime_opts) end) end @doc "Remove the business cover photo." @spec remove_business_cover_photo(connection(), String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def remove_business_cover_photo(connection, cover_id, opts \\ []) when is_binary(cover_id) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Business.remove_cover_photo(queryable, cover_id, runtime_opts) end) end @doc "Fetch business catalog collections." @spec business_collections(connection(), String.t() | nil, pos_integer(), keyword()) :: {:ok, map()} | {:error, term()} def business_collections(connection, jid \\ nil, limit \\ 51, opts \\ []) when (is_binary(jid) or is_nil(jid)) and is_integer(limit) and limit > 0 and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Business.get_collections(queryable, jid, limit, runtime_opts) end) end @doc "Create a business product." @spec business_product_create(connection(), map(), keyword()) :: {:ok, map()} | {:error, term()} def business_product_create(connection, product, opts \\ []) when is_map(product) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Business.product_create(queryable, product, runtime_opts) end) end @doc "Update a business product." @spec business_product_update(connection(), String.t(), map(), keyword()) :: {:ok, map()} | {:error, term()} def business_product_update(connection, product_id, updates, opts \\ []) when is_binary(product_id) and is_map(updates) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Business.product_update(queryable, product_id, updates, runtime_opts) end) end @doc "Delete one or more business products." @spec business_product_delete(connection(), [String.t()], keyword()) :: {:ok, %{deleted: non_neg_integer()}} | {:error, term()} def business_product_delete(connection, product_ids, opts \\ []) when is_list(product_ids) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Business.product_delete(queryable, product_ids, runtime_opts) end) end @doc "Fetch business order details." @spec business_order_details(connection(), String.t(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def business_order_details(connection, order_id, token, opts \\ []) when is_binary(order_id) and is_binary(token) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Business.get_order_details(queryable, order_id, token, runtime_opts) end) end @doc "Fetch newsletter metadata by `:jid` or `:invite` key." @spec newsletter_metadata(connection(), :invite | :jid, String.t(), keyword()) :: {:ok, map() | nil} | {:error, term()} def newsletter_metadata(connection, type, key, opts \\ []) when type in [:invite, :jid] and is_binary(key) and is_list(opts) do with_queryable(connection, fn queryable -> Newsletter.metadata(queryable, type, key, opts) end) end @doc "Follow a newsletter." @spec newsletter_follow(connection(), String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def newsletter_follow(connection, newsletter_jid, opts \\ []) when is_binary(newsletter_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Newsletter.follow(queryable, newsletter_jid, opts) end) end @doc "Unfollow a newsletter." @spec newsletter_unfollow(connection(), String.t(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def newsletter_unfollow(connection, newsletter_jid, opts \\ []) when is_binary(newsletter_jid) and is_list(opts) do with_queryable(connection, fn queryable -> Newsletter.unfollow(queryable, newsletter_jid, opts) end) end @doc "Create a newsletter." @spec newsletter_create(connection(), String.t(), String.t() | nil, keyword()) :: {:ok, map()} | {:error, term()} def newsletter_create(connection, name, description \\ nil, opts \\ []) when is_binary(name) and (is_binary(description) or is_nil(description)) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.create(queryable, name, description, runtime_opts) end) end @doc "Delete a newsletter." @spec newsletter_delete(connection(), String.t(), keyword()) :: :ok | {:error, term()} def newsletter_delete(connection, newsletter_jid, opts \\ []) when is_binary(newsletter_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.delete(queryable, newsletter_jid, runtime_opts) end) end @doc "Update newsletter metadata." @spec newsletter_update(connection(), String.t(), map(), keyword()) :: {:ok, map()} | {:error, term()} def newsletter_update(connection, newsletter_jid, updates, opts \\ []) when is_binary(newsletter_jid) and is_map(updates) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.update(queryable, newsletter_jid, updates, runtime_opts) end) end @doc "Fetch newsletter subscriber counts." @spec newsletter_subscribers(connection(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def newsletter_subscribers(connection, newsletter_jid, opts \\ []) when is_binary(newsletter_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.subscribers(queryable, newsletter_jid, runtime_opts) end) end @doc "Fetch newsletter admin count." @spec newsletter_admin_count(connection(), String.t(), keyword()) :: {:ok, non_neg_integer()} | {:error, term()} def newsletter_admin_count(connection, newsletter_jid, opts \\ []) when is_binary(newsletter_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.admin_count(queryable, newsletter_jid, runtime_opts) end) end @doc "Mute a newsletter." @spec newsletter_mute(connection(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def newsletter_mute(connection, newsletter_jid, opts \\ []) when is_binary(newsletter_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.mute(queryable, newsletter_jid, runtime_opts) end) end @doc "Unmute a newsletter." @spec newsletter_unmute(connection(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def newsletter_unmute(connection, newsletter_jid, opts \\ []) when is_binary(newsletter_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.unmute(queryable, newsletter_jid, runtime_opts) end) end @doc "Subscribe to live newsletter updates." @spec newsletter_subscribe_updates(connection(), String.t(), keyword()) :: {:ok, %{duration: String.t()} | nil} | {:error, term()} def newsletter_subscribe_updates(connection, newsletter_jid, opts \\ []) when is_binary(newsletter_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.subscribe_updates(queryable, newsletter_jid, runtime_opts) end) end @doc "Fetch newsletter message history." @spec newsletter_fetch_messages(connection(), String.t(), pos_integer(), keyword()) :: {:ok, BaileysEx.BinaryNode.t()} | {:error, term()} def newsletter_fetch_messages(connection, newsletter_jid, count, opts \\ []) when is_binary(newsletter_jid) and is_integer(count) and count > 0 and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.fetch_messages(queryable, newsletter_jid, count, runtime_opts) end) end @doc "React to a newsletter message or remove an existing reaction." @spec newsletter_react_message( connection(), String.t(), String.t(), String.t() | nil, keyword() ) :: :ok | {:error, term()} def newsletter_react_message(connection, newsletter_jid, server_id, reaction \\ nil, opts \\ []) when is_binary(newsletter_jid) and is_binary(server_id) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.react_message(queryable, newsletter_jid, server_id, reaction, runtime_opts) end) end @doc "Update the newsletter name." @spec newsletter_update_name(connection(), String.t(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def newsletter_update_name(connection, newsletter_jid, name, opts \\ []) when is_binary(newsletter_jid) and is_binary(name) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.update_name(queryable, newsletter_jid, name, runtime_opts) end) end @doc "Update the newsletter description." @spec newsletter_update_description(connection(), String.t(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def newsletter_update_description(connection, newsletter_jid, description, opts \\ []) when is_binary(newsletter_jid) and is_binary(description) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.update_description(queryable, newsletter_jid, description, runtime_opts) end) end @doc "Update the newsletter picture." @spec newsletter_update_picture(connection(), String.t(), term(), keyword()) :: {:ok, map()} | {:error, term()} def newsletter_update_picture(connection, newsletter_jid, content, opts \\ []) when is_binary(newsletter_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.update_picture(queryable, newsletter_jid, content, runtime_opts) end) end @doc "Remove the newsletter picture." @spec newsletter_remove_picture(connection(), String.t(), keyword()) :: {:ok, map()} | {:error, term()} def newsletter_remove_picture(connection, newsletter_jid, opts \\ []) when is_binary(newsletter_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.remove_picture(queryable, newsletter_jid, runtime_opts) end) end @doc "Change the newsletter owner." @spec newsletter_change_owner(connection(), String.t(), String.t(), keyword()) :: :ok | {:error, term()} def newsletter_change_owner(connection, newsletter_jid, new_owner_jid, opts \\ []) when is_binary(newsletter_jid) and is_binary(new_owner_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.change_owner(queryable, newsletter_jid, new_owner_jid, runtime_opts) end) end @doc "Demote a newsletter admin." @spec newsletter_demote(connection(), String.t(), String.t(), keyword()) :: :ok | {:error, term()} def newsletter_demote(connection, newsletter_jid, user_jid, opts \\ []) when is_binary(newsletter_jid) and is_binary(user_jid) and is_list(opts) do with_runtime(connection, opts, fn queryable, runtime_opts -> Newsletter.demote(queryable, newsletter_jid, user_jid, runtime_opts) end) end defp maybe_put_initial_event_subscribers(connection_opts, callback_opts) do if callback_handlers?(callback_opts) do handler = fn events -> dispatch_callback_events(events, callback_opts) end Keyword.update(connection_opts, :initial_event_subscribers, [handler], &[handler | &1]) else connection_opts end end defp callback_handlers?(callback_opts) when is_list(callback_opts) do Enum.any?(callback_opts, fn {_key, callback} -> is_function(callback, 1) end) end defp dispatch_callback_events(events, callback_opts) do maybe_emit_callback(callback_opts[:on_event], events) maybe_dispatch_connection_callback(events, callback_opts) maybe_dispatch_message_callback(events, callback_opts[:on_message]) end defp maybe_dispatch_connection_callback(events, callback_opts) do case events[:connection_update] do %{} = update -> maybe_emit_callback(callback_opts[:on_connection], update) maybe_emit_callback(callback_opts[:on_qr], Map.get(update, :qr)) _ -> :ok end end defp maybe_dispatch_message_callback(events, callback) do case get_in(events, [:messages_upsert, :messages]) do messages when is_list(messages) and is_function(callback, 1) -> Enum.each(messages, callback) _ -> :ok end end defp maybe_emit_callback(callback, payload) when is_function(callback, 1) do if is_binary(payload) or is_map(payload) do callback.(payload) else :ok end end defp maybe_emit_callback(_callback, _payload), do: :ok defp dispatch_public_events(events, handler) do require Logger try do events |> ordered_public_events() |> Enum.each(&handler.(&1)) rescue error -> Logger.error( "[BaileysEx] dispatch_public_events crashed: #{Exception.message(error)}\n" <> " events keys: #{inspect(Map.keys(events))}\n" <> " #{Exception.format_stacktrace(__STACKTRACE__)}" ) end end defp ordered_public_events(events) when is_map(events) do connection_events = case events[:connection_update] do %{} = update -> [{:connection, update}] _ -> [] end message_events = case get_in(events, [:messages_upsert, :messages]) do messages when is_list(messages) -> Enum.map(messages, &{:message, &1}) _ -> [] end presence_events = case events[:presence_update] do %{} = update -> [{:presence, update}] _ -> [] end call_events = case events[:call] do nil -> [] payload -> [{:call, payload}] end generic_events = events |> Enum.reject(fn {key, _value} -> key in [:call, :connection_update, :messages_upsert, :presence_update] end) |> Enum.sort_by(fn {key, _value} -> Atom.to_string(key) end) |> Enum.map(fn {key, value} -> {:event, key, value} end) connection_events ++ message_events ++ presence_events ++ call_events ++ generic_events end defp with_queryable(connection, fun) when is_function(fun, 1) do with {:ok, queryable} <- queryable(connection) do fun.(queryable) end end defp with_runtime(connection, opts, fun) when is_list(opts) and is_function(fun, 2) do with {:ok, queryable} <- queryable(connection) do fun.(queryable, build_runtime_opts(connection, opts)) end end defp with_app_state_runtime(connection, action, jid, value, opts) when is_atom(action) and is_binary(jid) and is_list(opts) do with {:ok, queryable} <- queryable(connection), {:ok, store_ref} <- runtime_store_ref(connection) do AppState.chat_modify( queryable, store_ref, action, jid, value, build_runtime_opts(connection, opts, store_ref) ) end end defp maybe_put_signal_store(opts, connection) do case runtime_signal_store(connection) do %SignalStore{} = store -> Keyword.put_new(opts, :signal_store, store) nil -> opts end end defp build_runtime_opts(connection, opts, store_ref \\ nil) when is_list(opts) do event_emitter = runtime_event_emitter(connection) me = runtime_me(connection, store_ref) [] |> maybe_put_keyword(:store, store_ref || runtime_store_ref_value(connection)) |> maybe_put_keyword(:creds_store, store_ref || runtime_store_ref_value(connection)) |> maybe_put_keyword(:signal_store, runtime_signal_store(connection)) |> maybe_put_keyword(:event_emitter, event_emitter) |> maybe_put_keyword(:me, me) |> maybe_put_keyword(:message_update_fun, runtime_message_update_fun(event_emitter)) |> maybe_put_keyword(:upsert_message_fun, runtime_upsert_message_fun(event_emitter)) |> Keyword.merge(opts) end defp runtime_store_ref(connection) do case ConnectionSupervisor.store(connection) do pid when is_pid(pid) -> {:ok, RuntimeStore.wrap(pid)} _ -> {:error, :store_not_available} end end defp runtime_store_ref_value(connection) do case runtime_store_ref(connection) do {:ok, store_ref} -> store_ref {:error, _reason} -> nil end end defp runtime_signal_store(connection) do case signal_store(connection) do {:ok, store} -> store {:error, _reason} -> nil end end defp runtime_event_emitter(connection) do case event_emitter(connection) do {:ok, pid} -> pid {:error, _reason} -> nil end end defp runtime_me(connection, %RuntimeStore.Ref{} = store_ref) do store_ref |> RuntimeStore.get(:creds, %{}) |> extract_me() |> case do nil -> runtime_me(connection, nil) me -> me end end defp runtime_me(connection, _store_ref) do case auth_state(connection) do {:ok, auth_state} -> extract_me(auth_state) {:error, _reason} -> nil end end defp runtime_message_update_fun(nil), do: nil defp runtime_message_update_fun(event_emitter) do fn payload -> EventEmitter.emit(event_emitter, :messages_update, payload) end end defp runtime_upsert_message_fun(nil), do: nil defp runtime_upsert_message_fun(event_emitter) do fn message -> EventEmitter.emit(event_emitter, :messages_upsert, %{type: :notify, messages: [message]}) end end defp runtime_send_node_fun({module, server}) when is_atom(module) do fn node -> module.send_node(server, node) end end defp normalize_privacy_settings(settings) when is_map(settings) do Map.put( settings, :readreceipts, Map.get(settings, :readreceipts) || Map.get(settings, "readreceipts") ) end defp maybe_put_presence_identity(opts, connection) do me = runtime_me(connection, runtime_store_ref_value(connection)) opts |> Keyword.put_new(:me_id, nested_id(me)) |> Keyword.put_new(:me_lid, nested_lid(me)) end defp nested_id(%{me: %{id: id}}) when is_binary(id), do: id defp nested_id(%{me: %{"id" => id}}) when is_binary(id), do: id defp nested_id(%{"me" => %{id: id}}) when is_binary(id), do: id defp nested_id(%{"me" => %{"id" => id}}) when is_binary(id), do: id defp nested_id(%{id: id}) when is_binary(id), do: id defp nested_id(%{"id" => id}) when is_binary(id), do: id defp nested_id(_auth_state), do: nil defp nested_lid(%{me: %{lid: lid}}) when is_binary(lid), do: lid defp nested_lid(%{me: %{"lid" => lid}}) when is_binary(lid), do: lid defp nested_lid(%{"me" => %{lid: lid}}) when is_binary(lid), do: lid defp nested_lid(%{"me" => %{"lid" => lid}}) when is_binary(lid), do: lid defp nested_lid(%{lid: lid}) when is_binary(lid), do: lid defp nested_lid(%{"lid" => lid}) when is_binary(lid), do: lid defp nested_lid(_auth_state), do: nil defp extract_me(%{me: %{} = me}), do: me defp extract_me(%{"me" => %{} = me}), do: me defp extract_me(%{creds: %{me: %{} = me}}), do: me defp extract_me(%{creds: %{"me" => %{} = me}}), do: me defp extract_me(%{"creds" => %{me: %{} = me}}), do: me defp extract_me(%{"creds" => %{"me" => %{} = me}}), do: me defp extract_me(_auth_state), do: nil defp maybe_put_keyword(opts, _key, nil), do: opts defp maybe_put_keyword(opts, key, value), do: Keyword.put_new(opts, key, value) defp normalize_jid(%JID{} = jid), do: {:ok, jid} defp normalize_jid(jid) when is_binary(jid) do case JIDUtil.parse(jid) do %JID{} = parsed -> {:ok, parsed} _ -> {:error, :invalid_jid} end end defp normalize_jid(_jid), do: {:error, :invalid_jid} end