wa

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// CallStateQueued is a call that has been asked for but not yet dialled, because another call
	// holds the single call slot. See callqueue.go.
	CallStateQueued   = "queued"
	CallStateRinging  = "ringing"
	CallStateAccepted = "accepted"
	CallStateRejected = "rejected"
	CallStateEnded    = "ended"
	CallStateFailed   = "failed"
	CallDirectionOut  = "outgoing"
	CallDirectionIn   = "incoming"
)

Call state machine. Calls only ever move forward through these states.

View Source
const (
	MatchAlways   = "always"
	MatchContains = "contains"
	MatchExact    = "exact"
	MatchPrefix   = "prefix"
	MatchSuffix   = "suffix"
	MatchRegex    = "regex"
)

Trigger match types.

View Source
const (
	ScopeAll    = "all"
	ScopeDMs    = "dms"
	ScopeGroups = "groups"
	ScopeList   = "list"
)

Trigger chat scopes.

View Source
const (
	ActionSendText  = "send_text"
	ActionSendMedia = "send_media"
	ActionReact     = "react"
	ActionMarkRead  = "mark_read"
	ActionWebhook   = "webhook"
	ActionForward   = "forward"
)

Trigger action types.

View Source
const (
	EventIncomingMessage = "incoming_message"
	EventOutgoingMessage = "outgoing_message"
	EventCallIncoming    = "call.incoming"
	EventCallPlaced      = "call.placed"
	EventCallAccepted    = "call.accepted"
	EventCallEnded       = "call.ended"
	EventCallRejected    = "call.rejected"
	EventConnectionState = "connection_state"
	EventSyncComplete    = "sync_complete"
	EventTriggerFired    = "trigger_fired"
)

Event kinds a trigger can select. These are the same strings webhooks are subscribed to, so a trigger and a webhook can react to exactly the same things.

Variables

View Source
var (
	DataDir       string
	MediaDir      string
	CapturePath   string
	SessionDBPath string
	AppDBPath     string
	QRPath        string
	HTTPAddr      string

	// InitErr reports a failure to prepare the data directory. A library must not exit the process,
	// so init records the problem and OpenStore/NewService refuse to start with it.
	InitErr error
)

Paths and settings for this wacli instance, resolved once from the environment.

WACLI_HOME lets several independent instances share one OS user, each with its own WhatsApp session, database and media; pair it with WACLI_HTTP_ADDR so each daemon binds its own port.

View Source
var ErrHistoryAnchorUnavailable = errors.New("history sync anchor unavailable")

Functions

func ClearSession

func ClearSession()

func DescribeCapture

func DescribeCapture(rec CaptureRecord) string

DescribeCapture renders one captured stanza as an indented tree, with binary content shown as hex plus an ASCII gloss. This is the view you want when working out what an unknown node means: it makes no assumptions about the protocol and hides nothing.

func KnownEvents

func KnownEvents() []string

KnownEvents lists every event kind wacli emits, for validation and for tooling that wants to present the choices.

func NewHTTPHandler

func NewHTTPHandler(service *Service) http.Handler

func NormalizePhone

func NormalizePhone(input string) string

Types

type ActionResult

type ActionResult struct {
	Type   string `json:"type"`
	OK     bool   `json:"ok"`
	Detail string `json:"detail,omitempty"`
	Error  string `json:"error,omitempty"`
}

ActionResult reports one action's outcome.

type AmbiguousReferenceError

type AmbiguousReferenceError struct {
	Reference  string
	Candidates []ResolvedTarget
}

func (*AmbiguousReferenceError) Error

func (e *AmbiguousReferenceError) Error() string

type AppLogRecord

type AppLogRecord struct {
	ID          int64     `json:"id"`
	Level       string    `json:"level"`
	Category    string    `json:"category"`
	Message     string    `json:"message"`
	DetailsJSON string    `json:"details_json,omitempty"`
	CreatedAt   time.Time `json:"created_at"`
}

type AudioRequest

type AudioRequest struct {
	// Say is spoken with the system speech synthesiser.
	Say string
	// Voice picks the synthesiser voice; empty means the system default.
	Voice string
	// File is a .wav/.mp3/.opus to play instead of Say.
	File string
	// Repeat restarts the audio when it finishes instead of hanging up.
	Repeat bool
	// Record is a path to write the other party's voice to, as a 16 kHz mono WAV.
	Record string
}

AudioRequest describes a call's audio in both directions. Say and File are mutually exclusive.

type AutoReplyRule

type AutoReplyRule struct {
	ID            int64     `json:"id"`
	Name          string    `json:"name"`
	MatchType     string    `json:"match_type"`
	Pattern       string    `json:"pattern,omitempty"`
	ReplyText     string    `json:"reply_text,omitempty"`
	MediaPath     string    `json:"media_path,omitempty"`
	Enabled       bool      `json:"enabled"`
	ApplyToDMs    bool      `json:"apply_to_dms"`
	ApplyToGroups bool      `json:"apply_to_groups"`
	Priority      int       `json:"priority"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
}

type BulkSendItem

type BulkSendItem struct {
	To        string `json:"to"`
	Text      string `json:"text,omitempty"`
	Message   string `json:"message,omitempty"`
	MediaPath string `json:"media_path,omitempty"`
}

type BulkSendResult

type BulkSendResult struct {
	To       string         `json:"to"`
	Resolved ResolvedTarget `json:"resolved,omitempty"`
	Success  bool           `json:"success"`
	Error    string         `json:"error,omitempty"`
	Message  MessageRecord  `json:"message,omitempty"`
}

type CallInfo

type CallInfo struct {
	// ShortID is a compact handle for this call — "c1", "c2", and so on — assigned when the call
	// enters the registry. WhatsApp's own call IDs are 22-character hex strings, which are awkward to
	// read back and easy to mistype or truncate; every command that takes a call accepts this
	// instead. It is unique for the lifetime of the daemon, which is also the lifetime of the
	// registry: calls are not persisted, so a restart starts again from c1.
	ShortID   string    `json:"short_id"`
	CallID    string    `json:"call_id"`
	PeerJID   string    `json:"peer_jid"`
	PeerName  string    `json:"peer_name,omitempty"`
	Direction string    `json:"direction"`
	State     string    `json:"state"`
	Video     bool      `json:"video"`
	StartedAt time.Time `json:"started_at"`
	EndedAt   time.Time `json:"ended_at,omitempty"`
	Reason    string    `json:"reason,omitempty"`

	// RecordPath is the absolute path the other party's voice is being written to, set when the call
	// was placed or answered with --record. Empty means recording was never asked for.
	RecordPath string `json:"record_path,omitempty"`
	// RecordedSeconds is how much inbound audio was actually captured, filled in when the call ends.
	// Zero on a finished call that had a RecordPath means no inbound audio ever arrived.
	RecordedSeconds float64 `json:"recorded_seconds"`

	// CallKey is the hex-encoded e2e media master key. Decrypting it out of the offer is not
	// something upstream whatsmeow does, and the media stack keeps its own key material, so nothing
	// populates this today; it is kept so the field does not vanish from the API.
	CallKey string `json:"call_key,omitempty"`
	// contains filtered or unexported fields
}

CallInfo is a single call, incoming or outgoing.

type CallStatusInfo

type CallStatusInfo struct {
	CallInfo
	// MediaLive reports whether the media stack still holds this call open.
	MediaLive bool `json:"media_live"`
	// DurationSeconds counts from the call starting, to now or to when it ended.
	DurationSeconds int `json:"duration_seconds"`
}

CallStatusInfo is a call plus the live media state the registry alone cannot report.

type CaptureRecord

type CaptureRecord struct {
	At        time.Time       `json:"at"`
	Direction string          `json:"direction"` // "recv" or "send"
	Node      json.RawMessage `json:"node"`
	XML       string          `json:"xml"`
}

CaptureRecord is one captured call stanza.

func LoadCaptures

func LoadCaptures(path string) ([]CaptureRecord, error)

LoadCaptures reads the capture file. Records that fail to parse are skipped.

type ChatRecord

type ChatRecord struct {
	JID                string    `json:"jid"`
	Name               string    `json:"name"`
	IsGroup            bool      `json:"is_group"`
	Locked             bool      `json:"locked"`
	FirstSeenAt        time.Time `json:"first_seen_at"`
	LastMessageAt      time.Time `json:"last_message_at"`
	LastMessagePreview string    `json:"last_message_preview"`
}

type ContactRecord

type ContactRecord struct {
	JID          string    `json:"jid"`
	Phone        string    `json:"phone"`
	FullName     string    `json:"full_name"`
	FirstName    string    `json:"first_name"`
	PushName     string    `json:"push_name"`
	BusinessName string    `json:"business_name"`
	Found        bool      `json:"found"`
	Bio          string    `json:"bio,omitempty"`
	Notes        string    `json:"notes,omitempty"`
	Memory       string    `json:"memory,omitempty"`
	MetadataJSON string    `json:"metadata_json,omitempty"`
	UpdatedAt    time.Time `json:"updated_at"`
}

type ContactUpdate

type ContactUpdate struct {
	Bio          *string `json:"bio,omitempty"`
	Notes        *string `json:"notes,omitempty"`
	Memory       *string `json:"memory,omitempty"`
	MetadataJSON *string `json:"metadata_json,omitempty"`
}

type GroupParticipant added in v0.3.0

type GroupParticipant struct {
	JID          string `json:"jid"`
	IsAdmin      bool   `json:"is_admin"`
	IsSuperAdmin bool   `json:"is_super_admin"`
}

GroupParticipant is one member and what they may do.

type GroupSummary added in v0.3.0

type GroupSummary struct {
	JID          string             `json:"jid"`
	Name         string             `json:"name"`
	Topic        string             `json:"topic,omitempty"`
	Owner        string             `json:"owner,omitempty"`
	Created      string             `json:"created,omitempty"`
	IsAnnounce   bool               `json:"is_announce"`
	IsLocked     bool               `json:"is_locked"`
	Participants []GroupParticipant `json:"participants,omitempty"`
}

GroupSummary is a group as wacli reports it. It is whatsmeow's GroupInfo flattened to the fields a caller acts on, so the JSON stays stable if the protocol struct grows.

type MessageRecord

type MessageRecord struct {
	ID          string    `json:"id"`
	ChatJID     string    `json:"chat_jid"`
	SenderJID   string    `json:"sender_jid"`
	Content     string    `json:"content"`
	Timestamp   time.Time `json:"timestamp"`
	IsFromMe    bool      `json:"is_from_me"`
	MessageType string    `json:"message_type"`
	// MentionsMe = this message @-mentions THIS account (the bot). QuotedIsFromMe
	// = it is a reply/quote to a message THIS account sent. Both let a consumer
	// (e.g. KARMAX) tell that the bot is being directly addressed — generically,
	// from the account's own identity, with no hardcoded numbers.
	MentionsMe     bool `json:"mentions_me,omitempty"`
	QuotedIsFromMe bool `json:"quoted_is_from_me,omitempty"`
	// MentionCount is how many JIDs this message @-mentioned in total, so a
	// consumer can tell a direct mention from an "@all"-style mass mention.
	// wacli deliberately doesn't judge that itself — that policy lives in the
	// consumer (e.g. the KARMAX wa-monitor loop).
	MentionCount  int    `json:"mention_count,omitempty"`
	MediaType     string `json:"media_type,omitempty"`
	MimeType      string `json:"mime_type,omitempty"`
	FileName      string `json:"file_name,omitempty"`
	MediaPath     string `json:"media_path,omitempty"`
	URL           string `json:"url,omitempty"`
	DirectPath    string `json:"direct_path,omitempty"`
	FileLength    uint64 `json:"file_length,omitempty"`
	MediaKey      []byte `json:"-"`
	FileSHA256    []byte `json:"-"`
	FileEncSHA256 []byte `json:"-"`
}

type MessageSearchOptions

type MessageSearchOptions struct {
	ChatJID   string
	SenderJID string
	Query     string
	Limit     int
	FromMe    *bool
	MediaOnly bool
	Before    *time.Time
	After     *time.Time
}

type OnWhatsAppResult added in v0.3.0

type OnWhatsAppResult struct {
	Query        string `json:"query"`
	JID          string `json:"jid,omitempty"`
	IsRegistered bool   `json:"is_registered"`
	VerifiedName string `json:"verified_name,omitempty"`
}

OnWhatsAppResult reports whether a number has WhatsApp.

type PlaceCallOptions

type PlaceCallOptions struct {
	// Video places a video call instead of a voice call.
	Video bool
	// RingFor is how long to keep ringing before hanging up automatically. Zero means 45s.
	// A negative value disables the timeout, leaving the call up until EndCall is called.
	RingFor time.Duration

	// Audio is played into the call once the peer answers and media comes up.
	Audio AudioRequest
}

PlaceCallOptions are the knobs for PlaceCall.

type QueueStatus

type QueueStatus struct {
	ActiveCallID string   `json:"active_call_id,omitempty"`
	Busy         bool     `json:"busy"`
	Waiting      []string `json:"waiting"`
}

QueueStatus describes the call slot and everything waiting on it.

type ReceiptRecord

type ReceiptRecord struct {
	MessageID    string    `json:"message_id"`
	ChatJID      string    `json:"chat_jid"`
	RecipientJID string    `json:"recipient_jid"`
	Type         string    `json:"type"`
	Timestamp    time.Time `json:"timestamp"`
}

ReceiptRecord is a delivery/read receipt for a message from one recipient.

type ResolveOptions

type ResolveOptions struct {
	Kind        string
	Limit       int
	AllowDirect bool
}

type ResolvedTarget

type ResolvedTarget struct {
	Input            string         `json:"input"`
	JID              string         `json:"jid"`
	Name             string         `json:"name"`
	Phone            string         `json:"phone,omitempty"`
	IsGroup          bool           `json:"is_group"`
	Locked           bool           `json:"locked"`
	ExistsInChats    bool           `json:"exists_in_chats"`
	ExistsInContacts bool           `json:"exists_in_contacts"`
	MatchType        string         `json:"match_type"`
	Score            int            `json:"score"`
	Chat             *ChatRecord    `json:"chat,omitempty"`
	Contact          *ContactRecord `json:"contact,omitempty"`
}

func ResolveBestTarget

func ResolveBestTarget(store *Store, ref string, opts ResolveOptions) (ResolvedTarget, error)

func ResolveTargets

func ResolveTargets(store *Store, ref string, opts ResolveOptions) ([]ResolvedTarget, error)

type Service

type Service struct {
	// contains filtered or unexported fields
}

func NewService

func NewService(store *Store) (*Service, error)

func (*Service) AnswerWithAudio

func (s *Service) AnswerWithAudio(ref string, req AudioRequest) (string, error)

AnswerWithAudio accepts a ringing call and speaks into it.

func (*Service) BulkSend

func (s *Service) BulkSend(ctx context.Context, items []BulkSendItem, interval time.Duration) ([]BulkSendResult, error)

func (*Service) CallQueueStatus

func (s *Service) CallQueueStatus() QueueStatus

CallQueueStatus reports the current slot and queue.

func (*Service) CallStatus

func (s *Service) CallStatus(ref string) (CallStatusInfo, error)

CallStatus reports one call by short handle, full ID, or unique prefix.

It reads through to the media layer as well as the registry: the registry knows the signalling state, but only meowcaller knows whether the call is still carrying media, and the two can disagree — a call the peer has torn down stays "accepted" in the registry until the terminate stanza lands.

func (*Service) CheckOnWhatsApp added in v0.3.0

func (s *Service) CheckOnWhatsApp(ctx context.Context, phones []string) ([]OnWhatsAppResult, error)

CheckOnWhatsApp reports which of the given phone numbers are reachable on WhatsApp.

Worth calling before a bulk send: sending to a number with no account fails per-message, and the answer is cheap to get for the whole list at once.

func (*Service) Client

func (s *Service) Client() *whatsmeow.Client

Client exposes the underlying whatsmeow client for callers that need to drive login or reach a protocol feature wacli does not wrap.

func (*Service) Close

func (s *Service) Close() error

func (*Service) Connect

func (s *Service) Connect() error

func (*Service) CreateGroup added in v0.3.0

func (s *Service) CreateGroup(ctx context.Context, name string, participantRefs []string) (GroupSummary, error)

CreateGroup creates a group with the given members.

func (*Service) CurrentUserJID

func (s *Service) CurrentUserJID() string

func (*Service) CurrentUserLID

func (s *Service) CurrentUserLID() string

CurrentUserLID returns this device's LID, which call media needs: the peer derives the SSRC it expects from the LID, not from the phone-number JID (see ssrc.go).

func (*Service) Disconnect

func (s *Service) Disconnect()

func (*Service) DownloadMedia

func (s *Service) DownloadMedia(ctx context.Context, messageID, chatJID string) (string, error)

func (*Service) EditMessage

func (s *Service) EditMessage(ctx context.Context, chatRef, messageID, newText string) (MessageRecord, error)

EditMessage edits a previously-sent message's text (WhatsApp shows "edited").

func (*Service) EndCall

func (s *Service) EndCall(ctx context.Context, ref, reason string) (CallInfo, error)

EndCall hangs up an ongoing or ringing call. reason may be empty.

func (*Service) GroupInfo added in v0.3.0

func (s *Service) GroupInfo(ctx context.Context, ref string) (GroupSummary, error)

GroupInfo returns one group with its participants.

func (s *Service) GroupInfoFromLink(ctx context.Context, link string) (GroupSummary, error)

GroupInfoFromLink previews a group behind an invite link without joining it.

func (s *Service) GroupInviteLink(ctx context.Context, ref string, reset bool) (string, error)

GroupInviteLink returns the group's invite link, optionally revoking the old one first.

func (*Service) HangupMedia

func (s *Service) HangupMedia(callID string) error

HangupMedia ends a live media call.

func (*Service) HistoryMarker

func (s *Service) HistoryMarker() int

func (*Service) IsConnected

func (s *Service) IsConnected() bool
func (s *Service) JoinGroupWithLink(ctx context.Context, link string) (GroupSummary, error)

JoinGroupWithLink joins a group from an invite link or bare code.

func (*Service) LatestRingingCall

func (s *Service) LatestRingingCall() (CallInfo, bool)

LatestRingingCall returns the most recent incoming call still ringing, if any.

func (*Service) LeaveGroup added in v0.3.0

func (s *Service) LeaveGroup(ctx context.Context, ref string) error

LeaveGroup leaves a group.

func (*Service) ListCalls

func (s *Service) ListCalls(activeOnly bool) []CallInfo

ListCalls returns known calls, newest first.

func (*Service) ListGroups added in v0.3.0

func (s *Service) ListGroups(ctx context.Context) ([]GroupSummary, error)

ListGroups returns every group this account belongs to.

func (*Service) MarkChatRead

func (s *Service) MarkChatRead(ctx context.Context, chatRef string) error

MarkChatRead sends read receipts for a chat's unread messages, which is what clears the unread badge on the other devices of this account.

func (*Service) MessageReceipts

func (s *Service) MessageReceipts(messageID string) ([]ReceiptRecord, error)

MessageReceipts returns the delivery/read receipts recorded for a message.

func (*Service) PlaceCall

func (s *Service) PlaceCall(ctx context.Context, recipient string, opts PlaceCallOptions) (CallInfo, error)

PlaceCall rings the given contact, and carries audio if the options ask for it.

The offer goes out through meowcaller rather than whatsmeow's OfferCall: a call we place gets its relay allocation in <ack class="call" type="offer">, which whatsmeow drops unhandled, so an offer it builds can ring but never carry media. See callmedia.go.

func (*Service) ReactToMessage

func (s *Service) ReactToMessage(ctx context.Context, chatRef, messageID, emoji string) error

ReactToMessage sends an emoji reaction to a message. An empty emoji removes an existing reaction, which is how WhatsApp itself models "un-react".

func (*Service) RefreshMissingChatNames

func (s *Service) RefreshMissingChatNames(ctx context.Context, limit int) error

func (*Service) RejectIncomingCall

func (s *Service) RejectIncomingCall(ctx context.Context, ref string) (CallInfo, error)

RejectIncomingCall declines a call that is currently ringing.

func (*Service) ReplayDelivery added in v0.3.0

func (s *Service) ReplayDelivery(deliveryID int64) (WebhookDeliveryRecord, error)

ReplayDelivery re-sends a recorded delivery, body unchanged.

Useful when a consumer was down, or was fixed after the fact: the event is still on disk, so it does not have to be recreated by hand.

func (*Service) RequestHistorySync

func (s *Service) RequestHistorySync(ctx context.Context, count int) error

func (*Service) ResolveBestTarget

func (s *Service) ResolveBestTarget(ref, kind string, allowDirect bool) (ResolvedTarget, error)

func (*Service) ResolveTargets

func (s *Service) ResolveTargets(ref, kind string, limit int, allowDirect bool) ([]ResolvedTarget, error)

func (*Service) RevokeMessage

func (s *Service) RevokeMessage(ctx context.Context, chatRef, messageID string) error

RevokeMessage deletes/revokes a message for everyone.

func (*Service) RunTriggers

func (s *Service) RunTriggers(event string, chat ChatRecord, message MessageRecord, dryRun bool) []TriggerResult

RunTriggers evaluates every trigger against an event and runs the ones that match.

Exported so an embedding caller can feed it events wacli did not generate itself. dryRun evaluates and reports without sending anything, which is what /triggers/test uses.

func (*Service) SearchMessages

func (s *Service) SearchMessages(chatRef, senderRef, query string, limit int, mediaOnly bool, fromMe string) ([]MessageRecord, *ResolvedTarget, *ResolvedTarget, error)

func (*Service) SearchMessagesAdvanced

func (s *Service) SearchMessagesAdvanced(chatRef, senderRef, query string, limit int, mediaOnly bool, fromMe string, before, after *time.Time) ([]MessageRecord, *ResolvedTarget, *ResolvedTarget, error)

func (*Service) SendMessage

func (s *Service) SendMessage(ctx context.Context, recipient, text, mediaPath string) (MessageRecord, error)

func (*Service) SendMessageReplying

func (s *Service) SendMessageReplying(ctx context.Context, recipient, text, mediaPath, replyToID string) (MessageRecord, error)

SendMessageReplying sends a message that, when replyToID is a message ID in the same chat, is delivered as a WhatsApp REPLY quoting that message — so the recipient sees which message it answers.

func (*Service) SendStory

func (s *Service) SendStory(ctx context.Context, text, mediaPath string) error

func (*Service) SetGroupName added in v0.3.0

func (s *Service) SetGroupName(ctx context.Context, ref, name string) error

SetGroupName renames a group.

func (*Service) SetGroupTopic added in v0.3.0

func (s *Service) SetGroupTopic(ctx context.Context, ref, topic string) error

SetGroupTopic sets a group's description.

func (*Service) SetPresence added in v0.3.0

func (s *Service) SetPresence(ctx context.Context, available bool) error

SetPresence announces this account as available or unavailable.

Presence is also a prerequisite rather than a nicety: WhatsApp only delivers some events to a device that has announced itself.

func (*Service) SetTyping added in v0.3.0

func (s *Service) SetTyping(ctx context.Context, chatRef string, typing bool, recording bool) error

SetTyping shows or clears the typing indicator in a chat, so an automated reply that takes a moment looks like someone composing rather than dead air.

func (*Service) StartCallCapture

func (s *Service) StartCallCapture() error

StartCallCapture begins recording raw call stanzas to the capture file.

func (*Service) StartConnectionWatchdog

func (s *Service) StartConnectionWatchdog()

StartConnectionWatchdog guards against the failure mode where the WhatsApp socket stops delivering messages while still reporting "connected" — the daemon looks healthy but silently receives nothing. It reconnects when the client reports disconnected, or when nothing at all has arrived for watchdogStaleAfter (receipts/presence make a truly silent window unusual on an active account). Idempotent: only the first call starts the loop.

func (*Service) StopCallCapture

func (s *Service) StopCallCapture() error

StopCallCapture stops recording call stanzas.

func (*Service) Store

func (s *Service) Store() *Store

Store exposes the local database: chats, messages, contacts, webhooks, triggers and settings.

func (*Service) SyncContacts

func (s *Service) SyncContacts(ctx context.Context) error

func (*Service) TestTrigger

func (s *Service) TestTrigger(id int64, chatRef, text string) (TriggerResult, error)

TestTrigger evaluates one trigger against a hypothetical message without sending anything.

func (*Service) TestWebhook added in v0.3.0

func (s *Service) TestWebhook(webhookID int64) (WebhookDeliveryRecord, error)

TestWebhook sends a synthetic event so an endpoint can be verified — reachability, status code and signature — without waiting for something real to happen.

func (*Service) UpdateGroupParticipants added in v0.3.0

func (s *Service) UpdateGroupParticipants(ctx context.Context, ref, action string, participantRefs []string) ([]GroupParticipant, error)

UpdateGroupParticipants adds, removes, promotes or demotes members.

action is add, remove, promote or demote. The per-participant outcome is returned rather than a single error, because WhatsApp routinely accepts some changes and refuses others in one request — a number that is not on WhatsApp, or someone who left.

func (*Service) WaitForBootstrapSync

func (s *Service) WaitForBootstrapSync(maxWait, quietPeriod time.Duration) bool

func (*Service) WaitForHistoryQuiet

func (s *Service) WaitForHistoryQuiet(marker int, maxWait, quietPeriod time.Duration) bool

type StatusSnapshot

type StatusSnapshot struct {
	Connected               bool       `json:"connected"`
	UserJID                 string     `json:"user_jid,omitempty"`
	UserLID                 string     `json:"user_lid,omitempty"`
	DNDMode                 bool       `json:"dnd_mode"`
	InitialAccessConfigured bool       `json:"initial_access_configured"`
	ChatCount               int        `json:"chat_count"`
	MessageCount            int        `json:"message_count"`
	LastHistorySync         *time.Time `json:"last_history_sync,omitempty"`
}

type Store

type Store struct {
	// contains filtered or unexported fields
}

func OpenStore

func OpenStore(path string) (*Store, error)

func (*Store) AddAppLog

func (s *Store) AddAppLog(level, category, message string, details any) error

func (*Store) AddAutoReply

func (s *Store) AddAutoReply(rule AutoReplyRule) (AutoReplyRule, error)

func (*Store) AddWebhook

func (s *Store) AddWebhook(record WebhookRecord) (WebhookRecord, error)

func (*Store) BuildStatus

func (s *Store) BuildStatus(connected bool, userJID string) (StatusSnapshot, error)

func (*Store) Close

func (s *Store) Close() error

func (*Store) ConfigureInitialAccess

func (s *Store) ConfigureInitialAccess(unlockedJIDs []string) error

func (*Store) CreateTrigger

func (s *Store) CreateTrigger(t Trigger) (Trigger, error)

CreateTrigger validates and stores a new trigger.

func (*Store) DeleteAutoReply

func (s *Store) DeleteAutoReply(id int64) error

func (*Store) DeleteTrigger

func (s *Store) DeleteTrigger(id int64) error

DeleteTrigger removes a trigger.

func (*Store) DeleteWebhook

func (s *Store) DeleteWebhook(id int64) error

func (*Store) EnsureChat

func (s *Store) EnsureChat(jid, name string, isGroup bool, messageTime time.Time, preview string, duringInitialSync bool) (ChatRecord, error)

func (*Store) FinishWebhookDelivery

func (s *Store) FinishWebhookDelivery(id int64, status string, httpStatus int, lastError, responseBody string) error

func (*Store) FinishWebhookDeliveryAttempt added in v0.3.0

func (s *Store) FinishWebhookDeliveryAttempt(id int64, status string, httpStatus int, lastError, responseBody string, attempts int) error

FinishWebhookDeliveryAttempt closes a delivery, recording how many attempts it took.

func (*Store) GetBoolSetting

func (s *Store) GetBoolSetting(key string, defaultValue bool) (bool, error)

func (*Store) GetChat

func (s *Store) GetChat(jid string) (ChatRecord, error)

func (*Store) GetContact

func (s *Store) GetContact(jid string) (ContactRecord, error)

func (*Store) GetDNDMode

func (s *Store) GetDNDMode() (bool, error)

func (*Store) GetMessage

func (s *Store) GetMessage(messageID, chatJID string) (MessageRecord, error)

func (*Store) GetTrigger

func (s *Store) GetTrigger(id int64) (Trigger, error)

GetTrigger returns one trigger by ID.

func (*Store) GetWebhook added in v0.3.0

func (s *Store) GetWebhook(id int64) (WebhookRecord, error)

GetWebhook returns one webhook subscription.

func (*Store) GetWebhookDelivery added in v0.3.0

func (s *Store) GetWebhookDelivery(id int64) (WebhookDeliveryRecord, error)

GetWebhookDelivery returns one delivery record, including the body needed to replay it.

func (*Store) InitialAccessConfigured

func (s *Store) InitialAccessConfigured() (bool, error)

func (*Store) JIDForNumber

func (s *Store) JIDForNumber(number, preferChatJID string) string

JIDForNumber finds the real JID for a bare number (e.g. "150285251002514") from known history/contacts — crucial for mentions, since a number can belong to an "@lid" identity and defaulting it to "@s.whatsapp.net" makes WhatsApp fail to render the mention. Prefers a JID seen in a specific chat, then any message sender, then a contact. Returns "" if the number is unknown.

func (*Store) LastHistorySync

func (s *Store) LastHistorySync() (*time.Time, error)

func (*Store) LatestMessage

func (s *Store) LatestMessage() (MessageRecord, error)

func (*Store) ListAppLogs

func (s *Store) ListAppLogs(limit int, level, category, query string) ([]AppLogRecord, error)

func (*Store) ListAutoReplies

func (s *Store) ListAutoReplies() ([]AutoReplyRule, error)

func (*Store) ListChats

func (s *Store) ListChats(filter string, limit int, query string) ([]ChatRecord, error)

func (*Store) ListContacts

func (s *Store) ListContacts(limit int, query string) ([]ContactRecord, error)

func (*Store) ListMessages

func (s *Store) ListMessages(chatJID string, limit int) ([]MessageRecord, error)

func (*Store) ListReceipts

func (s *Store) ListReceipts(messageID string) ([]ReceiptRecord, error)

ListReceipts returns all receipts for a message, oldest first.

func (*Store) ListTriggers

func (s *Store) ListTriggers() ([]Trigger, error)

ListTriggers returns every trigger in evaluation order: enabled first, then by priority, then ID.

func (*Store) ListWebhookDeliveries

func (s *Store) ListWebhookDeliveries(limit int, status, event, query string) ([]WebhookDeliveryRecord, error)

func (*Store) ListWebhooks

func (s *Store) ListWebhooks() ([]WebhookRecord, error)

func (*Store) MarkMessageDeleted

func (s *Store) MarkMessageDeleted(messageID, chatJID string) error

MarkMessageDeleted flags a message as revoked/deleted.

func (*Store) RecordWebhookRetry added in v0.3.0

func (s *Store) RecordWebhookRetry(id int64, attempts int, lastError string, httpStatus int, nextRetry time.Time) error

RecordWebhookRetry marks a delivery as awaiting another attempt, so an operator watching the delivery log can tell "retrying" apart from "given up".

func (*Store) SearchMessages

func (s *Store) SearchMessages(opts MessageSearchOptions) ([]MessageRecord, error)

func (*Store) SetBoolSetting

func (s *Store) SetBoolSetting(key string, value bool) error

func (*Store) SetChatLocked

func (s *Store) SetChatLocked(jid string, locked bool) error

func (*Store) SetDNDMode

func (s *Store) SetDNDMode(enabled bool) error

func (*Store) SetInitialAccessConfigured

func (s *Store) SetInitialAccessConfigured(enabled bool) error

func (*Store) SetLastHistorySync

func (s *Store) SetLastHistorySync(ts time.Time) error

func (*Store) SetTriggerEnabled

func (s *Store) SetTriggerEnabled(id int64, enabled bool) (Trigger, error)

SetTriggerEnabled flips a trigger on or off without rewriting it.

func (*Store) StartWebhookDelivery

func (s *Store) StartWebhookDelivery(webhookID int64, url, event, chatJID, messageID, requestBody string) (int64, error)

func (*Store) StoreMessage

func (s *Store) StoreMessage(record MessageRecord) error

func (*Store) StoreReceipt

func (s *Store) StoreReceipt(rec ReceiptRecord) error

StoreReceipt upserts a receipt (message + recipient + type is unique).

func (*Store) UpdateMessageContent

func (s *Store) UpdateMessageContent(messageID, chatJID, content string) error

UpdateMessageContent rewrites a message's content and flags it edited.

func (*Store) UpdateMessageMediaPath

func (s *Store) UpdateMessageMediaPath(messageID, chatJID, mediaPath string) error

func (*Store) UpdateTrigger

func (s *Store) UpdateTrigger(t Trigger) (Trigger, error)

UpdateTrigger replaces a trigger's definition, keeping its fire statistics.

func (*Store) UpsertContact

func (s *Store) UpsertContact(record ContactRecord) error

func (*Store) UpsertContactMemory

func (s *Store) UpsertContactMemory(jid string, update ContactUpdate) (ContactRecord, error)

type Trigger

type Trigger struct {
	ID      int64  `json:"id"`
	Name    string `json:"name"`
	Enabled bool   `json:"enabled"`
	// Priority orders evaluation, lowest first. Ties break by ID.
	Priority int `json:"priority"`

	// Events selects which event kinds fire this trigger. Empty means incoming_message, which is
	// what almost every rule wants.
	Events []string `json:"events"`
	// MatchType and Pattern filter on message text.
	MatchType string `json:"match_type"`
	Pattern   string `json:"pattern,omitempty"`
	// Scope limits which chats apply; ChatJIDs is the allow-list when Scope is "list".
	Scope    string   `json:"scope"`
	ChatJIDs []string `json:"chat_jids,omitempty"`
	// FromMe filters on message direction: nil matches either, false only inbound, true only our own.
	FromMe *bool `json:"from_me,omitempty"`

	// Actions run in order when the trigger matches.
	Actions []TriggerAction `json:"actions"`

	// StopOnMatch prevents lower-priority triggers from running once this one fires. Auto-replies
	// behaved this way, so it defaults to true on migrated rules.
	StopOnMatch bool `json:"stop_on_match"`
	// CooldownSeconds suppresses re-firing for the same chat within the window, so a chatty rule
	// cannot turn into a message loop.
	CooldownSeconds int `json:"cooldown_seconds"`

	FireCount   int64     `json:"fire_count"`
	LastFiredAt time.Time `json:"last_fired_at,omitempty"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

Trigger is one rule: what to match, and what to do about it.

func (*Trigger) Matches

func (t *Trigger) Matches(event string, chat ChatRecord, message MessageRecord) bool

Matches reports whether the trigger fires for this event, chat and message.

Exported so a caller can dry-run a rule against a message without sending anything — which is what the /triggers/test endpoint and the trigger_test tool use.

func (*Trigger) Validate

func (t *Trigger) Validate() error

Validate reports whether the trigger is coherent enough to store.

type TriggerAction

type TriggerAction struct {
	Type string `json:"type"`
	// Text is the message body for send_text/send_media, or the caption for media.
	Text string `json:"text,omitempty"`
	// MediaPath is an absolute path for send_media.
	MediaPath string `json:"media_path,omitempty"`
	// Emoji is the reaction for react.
	Emoji string `json:"emoji,omitempty"`
	// URL is the endpoint for webhook.
	URL string `json:"url,omitempty"`
	// To is the destination chat for forward, as a JID or a resolvable reference.
	To string `json:"to,omitempty"`
}

TriggerAction is one thing to do when a trigger fires.

Text fields expand a small set of placeholders — {{text}}, {{sender}}, {{sender_name}}, {{chat}}, {{chat_name}} — so a rule can answer with something derived from what arrived.

type TriggerResult

type TriggerResult struct {
	TriggerID   int64          `json:"trigger_id"`
	TriggerName string         `json:"trigger_name"`
	Matched     bool           `json:"matched"`
	Actions     []ActionResult `json:"actions,omitempty"`
	Skipped     string         `json:"skipped,omitempty"`
}

TriggerResult reports what one trigger did, for the API, the CLI and dry runs.

type WebhookDeliveryRecord

type WebhookDeliveryRecord struct {
	ID           int64  `json:"id"`
	WebhookID    int64  `json:"webhook_id"`
	URL          string `json:"url"`
	Event        string `json:"event"`
	ChatJID      string `json:"chat_jid,omitempty"`
	MessageID    string `json:"message_id,omitempty"`
	Status       string `json:"status"`
	HTTPStatus   int    `json:"http_status,omitempty"`
	LastError    string `json:"last_error,omitempty"`
	ResponseBody string `json:"response_body,omitempty"`
	RequestBody  string `json:"request_body,omitempty"`
	// Attempts is how many POSTs this delivery took. NextRetryAt is when the next one is due while
	// the status is "retrying".
	Attempts    int       `json:"attempts"`
	NextRetryAt time.Time `json:"next_retry_at,omitempty"`
	CreatedAt   time.Time `json:"created_at"`
	UpdatedAt   time.Time `json:"updated_at"`
}

type WebhookRecord

type WebhookRecord struct {
	ID           int64    `json:"id"`
	URL          string   `json:"url"`
	Secret       string   `json:"secret,omitempty"`
	Events       []string `json:"events"`
	Scope        string   `json:"scope"`
	ChatJIDs     []string `json:"chat_jids,omitempty"`
	MessageTypes []string `json:"message_types,omitempty"`
	ContextLimit int      `json:"context_limit"`
	Enabled      bool     `json:"enabled"`
	// IncludeMentions delivers messages from chats OUTSIDE this webhook's scope
	// when they @-mention this account, so a consumer can react to being
	// summoned anywhere. Pure transport: what to do with those events (e.g.
	// ignoring "@all" blasts) is the consumer's policy, not wacli's.
	IncludeMentions bool `json:"include_mentions"`
	// MaxAttempts bounds delivery retries; TimeoutSeconds bounds one attempt. Zero means the
	// defaults in webhookdelivery.go.
	MaxAttempts    int       `json:"max_attempts"`
	TimeoutSeconds int       `json:"timeout_seconds"`
	CreatedAt      time.Time `json:"created_at"`
	UpdatedAt      time.Time `json:"updated_at"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL