ExWapp.Call (ExWapp v0.1.2)

Copy Markdown View Source

Call log storage and queries.

One %ExWapp.Call{} describes one call: who started it, where it belongs, how it ended, and how long it lasted. Records are built from three sources and merged by call ID, so a call first seen live as <call> stanzas can later be enriched by the authoritative CallLogRecord the phone syncs:

  • :live — inbound <call> signalling stanzas (offer/accept/terminate/...)
  • :app_statecall_log app-state mutations carrying CallLogRecord
  • :history_syncHistorySync.callLogRecords entries

Storage

Records are persisted via the session's store adapter under the :call_log key, bounded to max_records entries (oldest dropped first) so an active account cannot grow the store without limit. The bound is configurable via config.calls.max_records.

Usage

# Most recent calls, newest first (limit defaults to 50)
calls = ExWapp.Call.list(session, limit: 20)

# Only missed incoming calls since a unix timestamp
missed = ExWapp.Call.list(session, status: :missed, since: 1_754_000_000)

# Deferred chronological traversal over the bounded retained log
ExWapp.Call.stream(session) |> Enum.each(&IO.inspect/1)

# Timestamps are unix seconds
DateTime.from_unix!(call.start_time)

Summary

Types

Lifecycle status of a call.

t()

Functions

Deletes every stored call record.

Counts stored call records.

Default bound on stored call records, used when no configuration overrides config.calls.max_records.

Deletes stored 1:1 call records for one peer.

Converts a CallLogRecord protobuf into a call struct.

Gets one stored call by call ID.

Lists stored calls, newest first by default.

Applies one CallLogRecord coming from app-state or history sync.

Builds a deferred stream over stored calls.

Inserts or merges one call record by ID and prunes the log to the bound.

Types

participant()

@type participant() :: %{jid: String.t() | nil, status: status()}

status()

@type status() ::
  :ringing
  | :ongoing
  | :connected
  | :missed
  | :rejected
  | :cancelled
  | :accepted_elsewhere
  | :failed
  | :unavailable
  | :upcoming
  | :abandoned
  | :invalid
  | :silenced
  | :unknown

Lifecycle status of a call.

:ringing and :ongoing are transient live states; everything else is a final outcome. :missed means an incoming call ended unanswered, :cancelled an outgoing call ended before the peer accepted, and :accepted_elsewhere that another device of this account took the call.

t()

@type t() :: %ExWapp.Call{
  accepted_at: non_neg_integer() | nil,
  call_type: :regular | :scheduled_call | :voice_chat | nil,
  chat_jid: String.t() | nil,
  creator_jid: String.t() | nil,
  duration: non_neg_integer() | nil,
  end_reason: String.t() | nil,
  ended_at: non_neg_integer() | nil,
  group_jid: String.t() | nil,
  id: String.t() | nil,
  is_group: boolean(),
  is_incoming: boolean() | nil,
  is_video: boolean() | nil,
  participants: [participant()],
  source: :live | :app_state | :history_sync | nil,
  start_time: non_neg_integer() | nil,
  status: status() | nil
}

target()

@type target() :: pid() | struct()

Functions

clear(target)

@spec clear(target()) :: :ok | {:error, term()}

Deletes every stored call record.

count(target)

@spec count(target()) :: non_neg_integer()

Counts stored call records.

default_max_records()

@spec default_max_records() :: pos_integer()

Default bound on stored call records, used when no configuration overrides config.calls.max_records.

delete_by_peer(target, peer_jid, is_incoming \\ nil)

@spec delete_by_peer(target(), String.t() | ExWapp.JID.t(), boolean() | nil) ::
  :ok | {:error, term()}

Deletes stored 1:1 call records for one peer.

Mirrors the app-state DeleteIndividualCallLogAction: is_incoming limits the deletion to one direction, nil removes both directions.

from_call_log_record(record, opts \\ [])

@spec from_call_log_record(
  WAWebProtobufSyncAction.CallLogRecord.t() | nil,
  keyword()
) :: t() | nil

Converts a CallLogRecord protobuf into a call struct.

Returns nil when the record has no call ID. :source defaults to :app_state; the history-sync ingester passes source: :history_sync.

get(target, call_id)

@spec get(target(), String.t()) :: {:ok, t()} | {:error, :not_found}

Gets one stored call by call ID.

list(target, opts \\ [])

@spec list(
  target(),
  keyword()
) :: [t()]

Lists stored calls, newest first by default.

Options

  • :limit — max records returned, :all for everything retained (default 50)
  • :offset — records skipped before taking :limit (default 0)
  • :order:newest_first (default) or :oldest_first
  • :status — a status/0 atom or list of them
  • :jid — only calls belonging to this chat/group JID
  • :since / :until — unix-second bounds on start_time

merge_from_sync(target, record, opts \\ [])

@spec merge_from_sync(
  target(),
  WAWebProtobufSyncAction.CallLogRecord.t() | nil,
  keyword()
) ::
  :ok | :ignored

Applies one CallLogRecord coming from app-state or history sync.

Returns :ok when the record was stored, :ignored when it carries no usable call ID.

stream(target, opts \\ [])

@spec stream(
  target(),
  keyword()
) :: Enumerable.t()

Builds a deferred stream over stored calls.

Defaults to oldest first with no limit so a wrapper can export the log in chronological order. The retained call-log snapshot is loaded and sorted on first enumeration, not when the stream is created; it is bounded by config.calls.max_records. Accepts the same options as list/2.

upsert(target, call, opts \\ [])

@spec upsert(target(), t(), keyword()) :: {:ok, t()} | {:error, term()}

Inserts or merges one call record by ID and prunes the log to the bound.

Field-level merge: incoming non-nil fields from an equally or more authoritative source win. App-state and history-sync records are authoritative over live signalling; a later live stanza may fill a missing field but cannot replace a synchronized value or outcome. Transient status progression is monotonic (:ringing then :ongoing) and never overwrites a final outcome. When both accepted_at and ended_at are known and duration is not, the duration is derived from their difference.

Options

  • :max_records — retention bound (default 500)