signalcli

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: 0BSD Imports: 16 Imported by: 0

README

signalcli

Go bindings for signal-cli's JSON-RPC API.

Installation

go get github.com/taigrr/signalcli

Prerequisites

signal-cli must be running in daemon mode:

signal-cli -a +1234567890 daemon --http 127.0.0.1:8080

Usage

Sending Messages
package main

import (
    "context"
    "log"

    "github.com/taigrr/signalcli"
)

func main() {
    client := signalcli.NewClient("http://localhost:8080", "+1234567890")
    ctx := context.Background()

    // Send a simple message
    result, err := client.Send(ctx, signalcli.SendParams{
        Recipient: "recipient-uuid-or-phone",
        Message:   "Hello from Go!",
    })
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("Sent at timestamp: %d", result.Timestamp)

    // Send with quote/reply
    _, err = client.Send(ctx, signalcli.SendParams{
        Recipient: "recipient-uuid",
        Message:   "This is a reply",
        Quote: &signalcli.Quote{
            Timestamp: 1234567890,
            Author:    "author-uuid",
        },
    })

    // Send to multiple recipients
    _, err = client.Send(ctx, signalcli.SendParams{
        Recipients: []string{"uuid1", "uuid2"},
        Message:    "Broadcast message",
    })

    // Send to a group
    _, err = client.Send(ctx, signalcli.SendParams{
        GroupID: "group-id",
        Message: "Hello group!",
    })
}
Reactions
// Add reaction
err := client.React(ctx, signalcli.ReactParams{
    Recipient:       "recipient-uuid",
    Emoji:           "👍",
    TargetAuthor:    "message-author-uuid",
    TargetTimestamp: 1234567890,
})

// Remove reaction
err = client.React(ctx, signalcli.ReactParams{
    Recipient:       "recipient-uuid",
    Emoji:           "👍",
    TargetAuthor:    "message-author-uuid",
    TargetTimestamp: 1234567890,
    Remove:          true,
})
Typing Indicator
// Start typing
err := client.SendTyping(ctx, signalcli.TypingParams{
    Recipient: "recipient-uuid",
})

// Stop typing
err = client.SendTyping(ctx, signalcli.TypingParams{
    Recipient: "recipient-uuid",
    Stop:      true,
})
Receiving Messages (SSE)
listener := signalcli.NewListener(client)

err := listener.Listen(ctx, func(env signalcli.Envelope) error {
    if env.DataMessage != nil {
        log.Printf("Message from %s: %s", 
            env.SourceName, 
            env.DataMessage.Message)
    }
    if env.TypingMessage != nil {
        log.Printf("Typing: %s from %s", 
            env.TypingMessage.Action, 
            env.SourceName)
    }
    return nil
})

Message Types

The library handles all signal-cli message types:

  • DataMessage - Regular text messages with attachments, mentions, reactions
  • SyncMessage - Messages synced from other devices
  • TypingMessage - Typing indicators
  • ReceiptMessage - Delivery and read receipts
  • CallMessage - Voice/video call events

Testing

go test ./...

License

0BSD

Documentation

Overview

Package signalcli provides Go bindings for signal-cli's JSON-RPC API.

signal-cli is a command-line interface for Signal that exposes a JSON-RPC API when run in daemon mode. This package provides a type-safe Go client for that API.

Example:

client := signalcli.NewClient("http://localhost:8080", "+1234567890")
err := client.Send(ctx, signalcli.SendParams{
    Recipient: "recipient-uuid",
    Message:   "Hello!",
})

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Address

type Address struct {
	UUID     string `json:"uuid,omitempty"`
	Number   string `json:"number,omitempty"`
	Username string `json:"username,omitempty"`
}

Address represents a Signal address.

type AnswerMessage

type AnswerMessage struct {
	ID int64 `json:"id"`
}

type Attachment

type Attachment struct {
	ContentType     string `json:"contentType"`
	Filename        string `json:"filename"`
	ID              string `json:"id"`
	Size            int64  `json:"size"`
	Width           int    `json:"width"`
	Height          int    `json:"height"`
	Caption         string `json:"caption"`
	UploadTimestamp int64  `json:"uploadTimestamp"`
}

Attachment represents a file attachment.

type BlockParams

type BlockParams struct {
	Recipient string `json:"recipient,omitempty"`
	GroupID   string `json:"groupId,omitempty"`
}

BlockParams contains parameters for blocking/unblocking.

type BusyMessage

type BusyMessage struct {
	ID int64 `json:"id"`
}

type CallMessage

type CallMessage struct {
	OfferMessage  *OfferMessage  `json:"offerMessage"`
	AnswerMessage *AnswerMessage `json:"answerMessage"`
	HangupMessage *HangupMessage `json:"hangupMessage"`
	BusyMessage   *BusyMessage   `json:"busyMessage"`
}

CallMessage represents a call event.

type Client

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

Client handles JSON-RPC communication with signal-cli.

func NewClient

func NewClient(baseURL, account string) *Client

NewClient creates a new signal-cli client.

baseURL is the signal-cli REST API URL (e.g., "http://localhost:8080"). account is the phone number or UUID of the account to use.

func (*Client) Account

func (c *Client) Account() string

Account returns the configured account.

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL returns the configured base URL.

func (*Client) Block

func (c *Client) Block(ctx context.Context, params BlockParams) error

Block blocks a recipient or group.

func (*Client) Call

func (c *Client) Call(ctx context.Context, method string, params any) (json.RawMessage, error)

Call makes a JSON-RPC call to signal-cli.

func (*Client) GetProfile

func (c *Client) GetProfile(ctx context.Context, recipient string) (*Profile, error)

GetProfile retrieves a user's profile.

func (*Client) ListContacts

func (c *Client) ListContacts(ctx context.Context) ([]Contact, error)

ListContacts retrieves all contacts for the configured account.

func (*Client) ListGroups

func (c *Client) ListGroups(ctx context.Context) ([]Group, error)

ListGroups retrieves all groups for the configured account.

func (*Client) MarkRead

func (c *Client) MarkRead(ctx context.Context, recipient string, timestamps []int64) error

MarkRead marks messages as read.

func (*Client) React

func (c *Client) React(ctx context.Context, params ReactParams) error

React sends a reaction to a message.

func (*Client) Send

func (c *Client) Send(ctx context.Context, params SendParams) (*SendResult, error)

Send sends a message.

func (*Client) SendTyping

func (c *Client) SendTyping(ctx context.Context, params TypingParams) error

SendTyping sends a typing indicator.

func (*Client) SetExpiration

func (c *Client) SetExpiration(ctx context.Context, recipient string, seconds int) error

SetExpiration sets the disappearing message timer for a conversation.

func (*Client) Unblock

func (c *Client) Unblock(ctx context.Context, params BlockParams) error

Unblock unblocks a recipient or group.

func (*Client) UpdateProfile

func (c *Client) UpdateProfile(ctx context.Context, params UpdateProfileParams) error

UpdateProfile updates the profile for the configured account.

func (*Client) WithHTTPClient

func (c *Client) WithHTTPClient(client *http.Client) *Client

WithHTTPClient sets a custom HTTP client.

type Contact

type Contact struct {
	Number    string  `json:"number"`
	UUID      string  `json:"uuid"`
	Name      string  `json:"name"`
	IsBlocked bool    `json:"isBlocked"`
	Color     string  `json:"color"`
	Address   Address `json:"address"`
}

Contact represents a Signal contact.

type Daemon

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

Daemon manages the signal-cli daemon subprocess.

func NewDaemon

func NewDaemon(cfg DaemonConfig) *Daemon

NewDaemon creates a new daemon manager.

func (*Daemon) BaseURL

func (d *Daemon) BaseURL() string

BaseURL returns the HTTP base URL for the daemon.

func (*Daemon) Error

func (d *Daemon) Error() error

Error returns the last error from the daemon.

func (*Daemon) IsReachable

func (d *Daemon) IsReachable(ctx context.Context) bool

IsReachable checks if signal-cli daemon is responding at the configured URL.

func (*Daemon) IsRunning

func (d *Daemon) IsRunning() bool

IsRunning checks if the daemon is currently running.

func (*Daemon) MemoryUsage

func (d *Daemon) MemoryUsage(ctx context.Context) (uint64, error)

MemoryUsage returns the resident set size (RSS) of the signal-cli process in bytes. It returns an error if the daemon is not running or its memory cannot be sampled.

signal-cli runs on the JVM and is prone to gradually ballooning memory; use this together with Watch to bound its footprint.

The RSS sampling is platform-specific (see memory_*.go): Linux reads /proc/<pid>/statm, macOS shells out to ps, and it is unsupported on Windows.

func (*Daemon) Restart

func (d *Daemon) Restart(ctx context.Context) error

Restart gracefully stops the daemon and starts it again.

Stop waits for signal-cli to exit so it can flush local state, and messages that arrive while the daemon is down are queued by the Signal servers and redelivered on reconnect — so no messages are dropped. A Listener started against this daemon reconnects automatically once the daemon is back up.

func (*Daemon) Start

func (d *Daemon) Start(ctx context.Context) error

Start starts the signal-cli daemon if not already running. It blocks until the daemon is ready to accept connections.

func (*Daemon) Stop

func (d *Daemon) Stop() error

Stop stops the signal-cli daemon.

func (*Daemon) Wait

func (d *Daemon) Wait() error

Wait waits for the daemon to exit.

func (*Daemon) Watch

func (d *Daemon) Watch(ctx context.Context, cfg WatchConfig) error

Watch samples the daemon's memory on an interval and restarts it when RSS stays above the configured limit for ConsecutiveHits samples. It blocks until ctx is cancelled (returning ctx.Err()) and is safe to run in its own goroutine alongside a Listener, which reconnects automatically across restarts.

type DaemonConfig

type DaemonConfig struct {
	// CLIPath is the path to the signal-cli binary.
	CLIPath string

	// Account is the phone number to use (e.g., "+1234567890").
	Account string

	// HTTPHost is the host to bind to (default "127.0.0.1").
	HTTPHost string

	// HTTPPort is the port to bind to (default 8080).
	HTTPPort int

	// ReceiveMode sets the receive mode (default "on-connection").
	ReceiveMode string

	// IgnoreAttachments skips downloading attachments.
	IgnoreAttachments bool

	// IgnoreStories skips story messages.
	IgnoreStories bool

	// SendReadReceipts sends read receipts automatically.
	SendReadReceipts bool

	// JavaMaxHeapMB caps the signal-cli JVM max heap via -Xmx (passed as
	// JAVA_OPTS to the subprocess). 0 means don't set it (JVM default, usually
	// ~1/4 of host RAM). When using Watch, this is auto-derived from the
	// watchdog limit if left 0. Set explicitly to cap the initial process too.
	JavaMaxHeapMB int
}

DaemonConfig holds configuration for the signal-cli daemon.

type DataMessage

type DataMessage struct {
	Timestamp        int64         `json:"timestamp"`
	Message          string        `json:"message"`
	ExpiresInSeconds int           `json:"expiresInSeconds"`
	ViewOnce         bool          `json:"viewOnce"`
	GroupInfo        *GroupInfo    `json:"groupInfo"`
	Quote            *QuoteInfo    `json:"quote"`
	Attachments      []Attachment  `json:"attachments"`
	Mentions         []MentionInfo `json:"mentions"`
	Reaction         *Reaction     `json:"reaction"`
	Sticker          *Sticker      `json:"sticker"`
}

DataMessage contains a regular text message.

type Envelope

type Envelope struct {
	Source            string          `json:"source"`
	SourceNumber      string          `json:"sourceNumber"`
	SourceUUID        string          `json:"sourceUuid"`
	SourceName        string          `json:"sourceName"`
	SourceDevice      int             `json:"sourceDevice"`
	Timestamp         int64           `json:"timestamp"`
	ServerReceivedAt  int64           `json:"serverReceivedTimestamp"`
	ServerDeliveredAt int64           `json:"serverDeliveredTimestamp"`
	DataMessage       *DataMessage    `json:"dataMessage"`
	SyncMessage       *SyncMessage    `json:"syncMessage"`
	TypingMessage     *TypingMessage  `json:"typingMessage"`
	ReceiptMessage    *ReceiptMessage `json:"receiptMessage"`
	CallMessage       *CallMessage    `json:"callMessage"`
}

Envelope is the top-level message envelope from signal-cli.

type EnvelopeHandler

type EnvelopeHandler func(Envelope) error

EnvelopeHandler is called for each received envelope.

type Group

type Group struct {
	ID                     string   `json:"id"`
	Name                   string   `json:"name"`
	Description            string   `json:"description"`
	IsMember               bool     `json:"isMember"`
	IsBlocked              bool     `json:"isBlocked"`
	Members                []string `json:"members"`
	PendingMembers         []string `json:"pendingMembers"`
	RequestingMembers      []string `json:"requestingMembers"`
	Admins                 []string `json:"admins"`
	PermissionAddMember    string   `json:"permissionAddMember"`
	PermissionEditDetails  string   `json:"permissionEditDetails"`
	PermissionSendMessages string   `json:"permissionSendMessage"`
	GroupInviteLink        string   `json:"groupInviteLink"`
}

Group represents a Signal group.

type GroupInfo

type GroupInfo struct {
	GroupID string `json:"groupId"`
	Type    string `json:"type"`
}

GroupInfo contains group chat information.

type HangupMessage

type HangupMessage struct {
	ID int64 `json:"id"`
}

type Listener

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

Listener handles Server-Sent Events from signal-cli for receiving messages.

func NewListener

func NewListener(client *Client) *Listener

NewListener creates a new SSE listener.

func (*Listener) Listen

func (l *Listener) Listen(ctx context.Context, handler EnvelopeHandler) error

Listen connects to SSE and calls the handler for each envelope. It automatically reconnects on connection errors.

type Mention

type Mention struct {
	Start  int    `json:"start"`
	Length int    `json:"length"`
	UUID   string `json:"uuid"`
}

Mention represents a user mention.

type MentionInfo

type MentionInfo struct {
	Start  int    `json:"start"`
	Length int    `json:"length"`
	UUID   string `json:"uuid"`
}

MentionInfo represents a user mention in a message.

type MessageEnvelope

type MessageEnvelope struct {
	Account  string   `json:"account"`
	Envelope Envelope `json:"envelope"`
}

MessageEnvelope wraps the envelope with account info.

type OfferMessage

type OfferMessage struct {
	ID   int64  `json:"id"`
	Type string `json:"type"`
}

type Profile

type Profile struct {
	Address   Address `json:"address"`
	Name      string  `json:"name"`
	IsBlocked bool    `json:"isBlocked"`
	ExpiresIn int     `json:"messageExpirationTime"`
}

Profile represents a Signal user profile.

type Quote

type Quote struct {
	Timestamp int64  `json:"timestamp"`
	Author    string `json:"author"`
	Message   string `json:"message,omitempty"`
}

Quote represents a quoted/replied message.

type QuoteInfo

type QuoteInfo struct {
	ID          int64        `json:"id"`
	Author      string       `json:"author"`
	AuthorUUID  string       `json:"authorUuid"`
	Text        string       `json:"text"`
	Attachments []Attachment `json:"attachments"`
}

QuoteInfo contains a quoted message.

type RPCError

type RPCError struct {
	Code    int             `json:"code"`
	Message string          `json:"message"`
	Data    json.RawMessage `json:"data,omitempty"`
}

RPCError represents a JSON-RPC error.

func (*RPCError) Error

func (e *RPCError) Error() string

type RPCRequest

type RPCRequest struct {
	JSONRPC string `json:"jsonrpc"`
	Method  string `json:"method"`
	Params  any    `json:"params,omitempty"`
	ID      string `json:"id"`
}

RPCRequest represents a JSON-RPC request.

type RPCResponse

type RPCResponse struct {
	JSONRPC string          `json:"jsonrpc"`
	Result  json.RawMessage `json:"result,omitempty"`
	Error   *RPCError       `json:"error,omitempty"`
	ID      string          `json:"id"`
}

RPCResponse represents a JSON-RPC response.

type ReactParams

type ReactParams struct {
	Recipient       string `json:"recipient"`         // Recipient UUID or phone
	Emoji           string `json:"emoji"`             // Emoji to react with
	TargetAuthor    string `json:"targetAuthor"`      // Author of target message
	TargetTimestamp int64  `json:"targetTimestamp"`   // Timestamp of target message
	Remove          bool   `json:"remove,omitempty"`  // Remove reaction instead of add
	GroupID         string `json:"groupId,omitempty"` // For group messages
}

ReactParams contains parameters for sending a reaction.

type Reaction

type Reaction struct {
	Emoji            string `json:"emoji"`
	TargetAuthor     string `json:"targetAuthor"`
	TargetAuthorUUID string `json:"targetAuthorUuid"`
	TargetTimestamp  int64  `json:"targetSentTimestamp"`
	IsRemove         bool   `json:"isRemove"`
}

Reaction contains reaction info.

type ReadMessage

type ReadMessage struct {
	Sender    string `json:"sender"`
	Timestamp int64  `json:"timestamp"`
}

ReadMessage indicates a message was read.

type ReceiptMessage

type ReceiptMessage struct {
	Type       string  `json:"type"` // "DELIVERY" or "READ"
	Timestamps []int64 `json:"timestamps"`
}

ReceiptMessage indicates message receipt status.

type RecipientResult

type RecipientResult struct {
	RecipientAddress    Address `json:"recipientAddress"`
	Type                string  `json:"type"` // "SUCCESS", "UNREGISTERED_FAILURE", etc.
	NetworkFailure      bool    `json:"networkFailure,omitempty"`
	UnregisteredFailure bool    `json:"unregisteredFailure,omitempty"`
}

RecipientResult contains the result for a single recipient.

type SendParams

type SendParams struct {
	Recipient   string    `json:"recipient,omitempty"`  // Single recipient (UUID or phone)
	Recipients  []string  `json:"recipients,omitempty"` // Multiple recipients
	GroupID     string    `json:"groupId,omitempty"`    // Group ID for group messages
	Message     string    `json:"message"`
	Attachments []string  `json:"attachments,omitempty"` // Paths to attachment files
	Quote       *Quote    `json:"quote,omitempty"`       // Quote/reply
	Mentions    []Mention `json:"mentions,omitempty"`    // @mentions
}

SendParams contains parameters for sending a message.

type SendResult

type SendResult struct {
	Timestamp int64             `json:"timestamp"`
	Results   []RecipientResult `json:"results"`
}

SendResult contains the result of sending a message.

type SentMessage

type SentMessage struct {
	Destination      string       `json:"destination"`
	DestinationUUID  string       `json:"destinationUuid"`
	Timestamp        int64        `json:"timestamp"`
	Message          string       `json:"message"`
	ExpiresInSeconds int          `json:"expiresInSeconds"`
	GroupInfo        *GroupInfo   `json:"groupInfo"`
	Attachments      []Attachment `json:"attachments"`
}

SentMessage is a message sent from another device.

type Sticker

type Sticker struct {
	PackID    string `json:"packId"`
	StickerID int    `json:"stickerId"`
}

Sticker represents a sticker.

type SyncMessage

type SyncMessage struct {
	SentMessage  *SentMessage  `json:"sentMessage"`
	ReadMessages []ReadMessage `json:"readMessages"`
}

SyncMessage contains a sync message (sent from another device).

type TypingMessage

type TypingMessage struct {
	Action    string `json:"action"` // "STARTED" or "STOPPED"
	Timestamp int64  `json:"timestamp"`
	GroupID   string `json:"groupId,omitempty"`
}

TypingMessage indicates typing status.

type TypingParams

type TypingParams struct {
	Recipient string `json:"recipient"`
	GroupID   string `json:"groupId,omitempty"`
	Stop      bool   `json:"stop,omitempty"`
}

TypingParams contains parameters for sending typing indicator.

type UpdateProfileParams

type UpdateProfileParams struct {
	Name       string `json:"name,omitempty"`
	About      string `json:"about,omitempty"`
	AboutEmoji string `json:"aboutEmoji,omitempty"`
	Avatar     string `json:"avatar,omitempty"` // Path to avatar file
}

UpdateProfileParams contains parameters for updating the user profile.

type WatchConfig

type WatchConfig struct {
	// MemoryLimit is the RSS threshold in bytes. When exceeded for
	// ConsecutiveHits samples the daemon is restarted. Required (> 0).
	MemoryLimit uint64

	// Interval is how often memory is sampled (default 30s).
	Interval time.Duration

	// ConsecutiveHits is the number of back-to-back over-limit samples
	// required before restarting, to ride out transient spikes (default 3).
	ConsecutiveHits int

	// OnRestart, if set, is called just before a restart is triggered with the
	// RSS that tripped the limit.
	OnRestart func(rss uint64)

	// OnError, if set, is called when sampling or restarting fails. The watch
	// loop continues regardless.
	OnError func(err error)
}

WatchConfig configures the memory watchdog.

Jump to

Keyboard shortcuts

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