signalbot

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: AGPL-3.0 Imports: 14 Imported by: 0

README

Signal Bot Framework

Go Reference

A Go module to build your own Signal bots asynchronously and easily.

This is a structural port of the Python signalbot framework, relying on signal-cli-rest-api for all HTTP and WebSocket communication with the Signal network.

Installation

go get github.com/dmitrii-codes/signal-go-bot

Prerequisites: Go 1.24 or newer and an active, registered instance of signal-cli-rest-api. This API container must be running in json-rpc mode to expose the required WebSocket.

Quickstart

This is what a minimal bot using signal-go-bot looks like:

package main

import (
	"log"

	"github.com/dmitrii-codes/signal-go-bot"
)

// Define a command
type PingCommand struct{}

func (c *PingCommand) Handle(ctx *signalbot.Context) error {
	log.Println("Received ping command")
	return ctx.Reply("pong")
}

func main() {
	// Initialize Bot connected to your local signal-cli-rest-api instance
	config := signalbot.NewConfig("127.0.0.1:8080", "+1234567890")
	bot := signalbot.NewBot(config)

	// Register Command with a case-insensitive trigger
	bot.Register(signalbot.Triggered(&PingCommand{}, false, "ping"))

	// Start bot loop and WebSocket connection (Blocking)
	if err := bot.Start(); err != nil {
		log.Fatalf("Failed to start bot: %v", err)
	}
}

Features

  • Asynchronous & Concurrent: Written natively in Go using Goroutines for handling inbound messages.
  • WebSocket Streaming: Built on gorilla/websocket for instant reaction times over the local network.
  • Triggers: Utilize robust middlewares like Triggered, RegexTriggered, and ReactionTriggered.
  • Quotations & Mentions: Support for rich message formatting and exact replies (ctx.Reply("...")).
  • Read Receipts & Typing Indicators: Easily emulate human-like behavior via commands (ctx.StartTyping(), ctx.MarkRead()).
  • Storage Subsystems: Optional JSON-backed key/value storage using SQLite or Redis.
  • Attachments: Download incoming attachments as base64 by default and send base64 attachments with SendOptions.

Architecture Pattern

Commands in Go implement a very simple interface rather than needing inheritance or decorators:

type Command interface {
	Handle(ctx *Context) error
}

You can wrap configurations, database connections, and memory state inside your Command structure easily:

type DatabaseCheckCommand struct {
	DB *sql.DB
}

func (c *DatabaseCheckCommand) Handle(ctx *signalbot.Context) error {
	// Execute custom DB logic
	return ctx.Send("Checked!", nil)
}

Each incoming message is handled in its own goroutine. For a given message, all registered commands run in registration order; trigger wrappers decide whether their wrapped command should run.

Configuration

NewConfig requires the signal-cli-rest-api address and the registered Signal number. The address must be host:port, because the library adds the HTTP and WebSocket schemes.

config := signalbot.NewConfig("127.0.0.1:8080", "+1234567890")

// Optional authentication for a protected signal-cli-rest-api instance.
config.Auth = &signalbot.BearerAuthentication{Token: "token"}

// Basic authentication is also supported:
// config.Auth = &signalbot.BasicAuthentication{Username: "user", Password: "pass"}

// Optional storage. Context.Storage is nil when no backend is configured.
storage, err := signalbot.NewSQLiteStorage("bot.db")
if err != nil {
	log.Fatal(err)
}
config.Storage = storage

// Incoming attachments are downloaded and base64-encoded by default.
// You can disable automatic downloads.
config.DownloadAttachments = false

Redis is available through NewRedisStorage(host, port, password). SQLite uses github.com/mattn/go-sqlite3, so builds that use it require CGO and a C compiler.

Documentation & Examples

See Getting Started for setup and trigger examples, and Context Reference for messages, sending options, storage, and the lower-level API.

You can also find runnable templates and advanced command examples in the examples/ directory.

Contributing

Pull requests are welcome! Feel free to open an issue or submit a PR if you encounter a bug or have a feature request. When contributing, please ensure tests pass (go test ./...) before submitting.

License

This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).

You are free to use, modify, and distribute this software.
However, if you run a modified version as a network service or distribute it, you must also make the source code available under the same license.

See the LICENSE file for the full text.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("key not found")

ErrNotFound is returned when a key is not found in the storage

Functions

func Bool

func Bool(v bool) *bool

func Int

func Int(v int) *int

func Int64

func Int64(v int64) *int64

func String

func String(v string) *string

Helper functions for pointers

Types

type API

type API struct {
	SignalService string
	PhoneNumber   string
	// contains filtered or unexported fields
}

func NewAPI

func NewAPI(config *Config) *API

func (*API) DeleteAttachment

func (api *API) DeleteAttachment(attachmentId string) error

Deletes an attachment by its ID from the local storage

func (*API) GetAttachment

func (api *API) GetAttachment(attachmentId string) ([]byte, error)

Retrieves the raw binary content of an attachment by its ID

func (*API) GetGroup

func (api *API) GetGroup(groupId string) (GroupResponse, error)

Retrieves information about a specific group by its ID

func (*API) GetGroups

func (api *API) GetGroups() ([]GroupResponse, error)

Retrieves the list of groups associated with the phone number

func (*API) HealthCheck

func (api *API) HealthCheck() error

Checks the health of the Signal service by sending a GET request to the /v1/health endpoint

func (*API) React

func (api *API) React(receiver, emoji, targetAuthor string, targetTimestamp int64) error

Sends a reaction to a message from a specific author at a given timestamp

func (*API) ReceiveMessages

func (api *API) ReceiveMessages(ctx context.Context) (<-chan Message, error)

Receives messages from via a WebSocket connection and returns a channel of Message objects

func (*API) RemoteDelete

func (api *API) RemoteDelete(receiver string, timestamp int64) error

Deletes a message from the recipient's device by specifying the receiver and timestamp

func (*API) SendMessage

func (api *API) SendMessage(receiver, text string, opts *SendOptions) (Message, error)

Sends a message to the specified receiver with optional parameters

func (*API) SendReceipt

func (api *API) SendReceipt(receiver, receiptType string, timestamp int64) error

Sends a receipt for a message to the specified receiver

func (*API) StartTyping

func (api *API) StartTyping(receiver string) error

Starts typing indicator for the specified receiver

func (*API) StopTyping

func (api *API) StopTyping(receiver string) error

Stops typing indicator for the specified receiver

func (*API) UpdateContact

func (api *API) UpdateContact(recipient, name, expirationInSeconds string) error

Updates a contact's name and/or expiration time for the specified recipient

type Authentication

type Authentication interface {
	AuthString() string
	ApplyTo(headers map[string]string)
}

type BasicAuthentication

type BasicAuthentication struct {
	Username string
	Password string
}

BasicAuthentication handles username/password based authentication

func (*BasicAuthentication) ApplyTo

func (auth *BasicAuthentication) ApplyTo(headers map[string]string)

func (*BasicAuthentication) AuthString

func (auth *BasicAuthentication) AuthString() string

type BearerAuthentication

type BearerAuthentication struct {
	Token string
}

BearerAuthentication handles token based authentication

func (*BearerAuthentication) ApplyTo

func (auth *BearerAuthentication) ApplyTo(headers map[string]string)

func (*BearerAuthentication) AuthString

func (auth *BearerAuthentication) AuthString() string

type Bot

type Bot struct {
	SignalService       string
	PhoneNumber         string
	Commands            []Command
	API                 *API
	Storage             Storage // Central storage interface
	DownloadAttachments bool
	// contains filtered or unexported fields
}

func NewBot

func NewBot(config *Config) *Bot

func (*Bot) MarkRead

func (b *Bot) MarkRead(receiver string, timestamp int64) error

func (*Bot) React

func (b *Bot) React(receiver, emoji, targetAuthor string, targetTimestamp int64) error

func (*Bot) Register

func (b *Bot) Register(cmd Command)

func (*Bot) Send

func (b *Bot) Send(receiver, text string, opts *SendOptions) (Message, error)

func (*Bot) Start

func (b *Bot) Start() error

func (*Bot) StartTyping

func (b *Bot) StartTyping(receiver string) error

func (*Bot) StopTyping

func (b *Bot) StopTyping(receiver string) error

type Command

type Command interface {
	Handle(ctx *Context) error
}

func ReactionTriggered

func ReactionTriggered(cmd Command, emojis ...string) Command

ReactionTriggered returns a command that only fires when a reaction matching the emojis is received

func RegexTriggered

func RegexTriggered(cmd Command, patterns ...*regexp.Regexp) Command

RegexTriggered returns a command that only fires if the message matches the regex

func Triggered

func Triggered(cmd Command, caseSensitive bool, exactMatches ...string) Command

Triggered returns a command that only fires if the message directly equals any of the variations

type CommandFunc

type CommandFunc func(ctx *Context) error

func (CommandFunc) Handle

func (f CommandFunc) Handle(ctx *Context) error

type Config

type Config struct {
	SignalService       string
	PhoneNumber         string
	Auth                Authentication
	Storage             Storage
	DownloadAttachments bool
}

Config holds all parameters to bootstrap a signalbot instance

func NewConfig

func NewConfig(signalService, phoneNumber string) *Config

Minimal config payload

type Context

type Context struct {
	Bot     *Bot
	Message Message
	Storage Storage
}

func NewContext

func NewContext(bot *Bot, msg Message) *Context

func (*Context) MarkRead

func (c *Context) MarkRead() error

func (*Context) React

func (c *Context) React(emoji string) error

func (*Context) Reply

func (c *Context) Reply(text string) error

func (*Context) Send

func (c *Context) Send(text string, opts *SendOptions) error

func (*Context) StartTyping

func (c *Context) StartTyping() error

func (*Context) StopTyping

func (c *Context) StopTyping() error

type GroupResponse

type GroupResponse struct {
	ID          string   `json:"id"`
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Members     []string `json:"members"`
}

type LinkPreview

type LinkPreview struct {
	URL         string `json:"url"`
	Title       string `json:"title"`
	Description string `json:"description"`
	Image       string `json:"image"`
}

type LinkPreviewRequest

type LinkPreviewRequest struct {
	URL         string `json:"url"`
	Title       string `json:"title"`
	Description string `json:"description"`
	Image       string `json:"image"`
}

type Message

type Message struct {
	Source                    string
	SourceNumber              *string
	SourceUUID                string
	Timestamp                 int64
	Type                      MessageType
	Text                      string
	Base64Attachments         []string
	AttachmentsLocalFilenames []string
	ViewOnce                  bool
	LinkPreviews              []LinkPreview
	Group                     *string
	Reaction                  *Reaction
	Mentions                  []string
	Quote                     *Quote
	ReadMessages              []json.RawMessage
	TargetSentTimestamp       *int64
	RemoteDeleteTimestamp     *int64
	UpdatedGroupID            *string
	RawMessage                string
}

func UnmarshalSignalJSON

func UnmarshalSignalJSON(payload []byte) (*Message, error)

UnmarshalSignalJSON processes the outer Payload Envelope from the websocket

func (*Message) IsGroup

func (m *Message) IsGroup() bool

func (*Message) IsPrivate

func (m *Message) IsPrivate() bool

func (*Message) Recipient

func (m *Message) Recipient() string

type MessageType

type MessageType int
const (
	SyncMessage MessageType = iota
	DataMessage
	EditMessage
	DeleteMessage
	ReadMessage
	GroupUpdateMessage
	ReactionMessage
	ContactSyncMessage
)

type Quote

type Quote struct {
	Timestamp int64  `json:"timestamp"`
	Author    string `json:"author"`
	Text      string `json:"text"`
}

type Reaction

type Reaction struct {
	Emoji               string `json:"emoji"`
	TargetAuthor        string `json:"target_author"`
	TargetSentTimestamp int64  `json:"target_sent_timestamp"`
	IsRemove            bool   `json:"is_remove"`
}

type ReactionRequest

type ReactionRequest struct {
	Recipient    string `json:"recipient"`
	Reaction     string `json:"reaction"`
	TargetAuthor string `json:"target_author"`
	Timestamp    int64  `json:"timestamp"`
}

type ReceiptRequest

type ReceiptRequest struct {
	Recipient   string `json:"recipient"`
	ReceiptType string `json:"receipt_type"`
	Timestamp   int64  `json:"timestamp"`
}

type RedisStorage

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

func NewRedisStorage

func NewRedisStorage(host string, port int, password string) *RedisStorage

func (*RedisStorage) Delete

func (s *RedisStorage) Delete(ctx context.Context, key string) error

func (*RedisStorage) Exists

func (s *RedisStorage) Exists(ctx context.Context, key string) (bool, error)

func (*RedisStorage) Read

func (s *RedisStorage) Read(ctx context.Context, key string, v any) error

func (*RedisStorage) Save

func (s *RedisStorage) Save(ctx context.Context, key string, v any) error

type RemoteDeleteRequest

type RemoteDeleteRequest struct {
	Recipient string `json:"recipient"`
	Timestamp int64  `json:"timestamp"`
}

type SQLiteStorage

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

func NewSQLiteStorage

func NewSQLiteStorage(dataSourceName string) (*SQLiteStorage, error)

func (*SQLiteStorage) Delete

func (s *SQLiteStorage) Delete(ctx context.Context, key string) error

func (*SQLiteStorage) Exists

func (s *SQLiteStorage) Exists(ctx context.Context, key string) (bool, error)

func (*SQLiteStorage) Read

func (s *SQLiteStorage) Read(ctx context.Context, key string, v any) error

func (*SQLiteStorage) Save

func (s *SQLiteStorage) Save(ctx context.Context, key string, v any) error

type SendMessageRequest

type SendMessageRequest struct {
	Message           string              `json:"message"`
	Number            string              `json:"number"`
	Recipients        []string            `json:"recipients"`
	QuoteAuthor       *string             `json:"quote_author,omitempty"`
	QuoteTimestamp    *int64              `json:"quote_timestamp,omitempty"`
	QuoteMessage      *string             `json:"quote_message,omitempty"`
	Base64Attachments []string            `json:"base64_attachments,omitempty"`
	LinkPreview       *LinkPreviewRequest `json:"link_preview,omitempty"`
	Mentions          []string            `json:"mentions,omitempty"`
	EditTimestamp     *int64              `json:"edit_timestamp,omitempty"`
	ViewOnce          *bool               `json:"view_once,omitempty"`
}

type SendOptions

type SendOptions struct {
	Base64Attachments []string
	LinkPreview       *LinkPreview
	Quote             *Quote
	Mentions          []string
	EditTimestamp     int64
	ViewOnce          bool
}

type Storage

type Storage interface {
	Exists(ctx context.Context, key string) (bool, error)
	Read(ctx context.Context, key string, v any) error
	Save(ctx context.Context, key string, v any) error
	Delete(ctx context.Context, key string) error
}

type TypingIndicatorRequest

type TypingIndicatorRequest struct {
	Recipient string `json:"recipient"`
}

type UpdateContactRequest

type UpdateContactRequest struct {
	Recipient           string  `json:"recipient"`
	Name                *string `json:"name,omitempty"`
	ExpirationInSeconds *string `json:"expiration_in_seconds,omitempty"`
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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