core

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 8, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package core holds botbooter's platform-agnostic engine: the Bot type, its command/middleware dispatch, and the connection lifecycle.

Index

Constants

This section is empty.

Variables

View Source
var ErrAlreadyConnected = errors.New("botbooter: already connected")

ErrAlreadyConnected is returned by Connect when the Bot is already connected.

View Source
var ErrNilMessage = errors.New("botbooter: nil message")

ErrNilMessage is returned by Bot methods handed a nil *Message argument.

View Source
var ErrUnknownBotType = errors.New("botbooter: unknown bot type")

ErrUnknownBotType is returned by Bot methods when the Bot has no adapter.

Functions

func AdapterAs added in v0.2.0

func AdapterAs[T any](b *Bot) (T, bool)

AdapterAs returns the Bot's adapter as T, reporting whether it is that type. Adapter packages use it to recover their concrete adapter from a *Bot.

Types

type Adapter

type Adapter interface {
	Connect(ctx context.Context, deps AdapterDeps) error
	Disconnect() error
	Send(ctx context.Context, channelID, text string, opts SendOptions) error
	Attachments(m *Message) ([]Attachment, error)
}

Adapter is the platform-specific half of a Bot. The Bot drives it through this interface, so the core has no compile-time dependency on any platform.

type AdapterDeps

type AdapterDeps struct {
	Dispatch   func(ctx context.Context, m *Message)
	Done       func(err error)
	Disconnect func() error
	Logger     *slog.Logger // always non-nil
}

AdapterDeps is the set of callbacks an Adapter uses to talk back to the Bot, plus the Bot's logger so adapter diagnostics route through the same sink.

type Attachment

type Attachment struct {
	IsImage   bool
	URL       string
	ExtraData any
}

Attachment is a platform-agnostic file attached to a message.

type AttachmentResolver added in v0.2.0

type AttachmentResolver interface {
	ResolveAttachmentURL(ctx context.Context, att Attachment) (string, error)
}

AttachmentResolver is an optional capability an Adapter may implement to turn an Attachment into a downloadable URL; adapters whose Attachment.URL is already usable ride the passthrough in Bot.ResolveAttachmentURL.

type Bot

type Bot struct {
	BotType BotType
	// contains filtered or unexported fields
}

Bot is the platform-agnostic chat bot. Register handlers and middleware before Connect; after that, Connect/Run/Disconnect/Send are safe to call concurrently. Registering after Connect races the dispatch goroutine.

func New

func New(botType BotType, adapter Adapter) *Bot

New creates a Bot of the given type backed by adapter.

func (*Bot) AddHandler

func (b *Bot) AddHandler(cmd Command) error

AddHandler registers cmd, compiling its Pattern and returning an error if it is not valid. Commands are matched in registration order, first match wins.

func (*Bot) AddMiddleware

func (b *Bot) AddMiddleware(middleware Middleware)

AddMiddleware appends middleware to the dispatch chain, run in registration order.

func (*Bot) Connect

func (b *Bot) Connect(ctx context.Context) error

Connect starts the adapter's event loop and returns without blocking. It returns ErrAlreadyConnected if a connection is already active, ErrUnknownBotType if the Bot has no adapter, or any error from the adapter's own Connect.

func (*Bot) Disconnect

func (b *Bot) Disconnect() error

Disconnect tears down the active connection: it cancels the run context and runs the adapter's Disconnect exactly once. It is safe to call when not connected, returning ErrUnknownBotType only if the Bot has no adapter.

func (*Bot) GetAttachments

func (b *Bot) GetAttachments(message *Message) ([]Attachment, error)

GetAttachments returns the platform-agnostic attachments of message. It returns ErrNilMessage if message is nil, or ErrUnknownBotType if the Bot has no adapter.

func (*Bot) HandleFunc

func (b *Bot) HandleFunc(pattern string, handler CommandHandler) error

HandleFunc is a convenience wrapper around AddHandler.

func (*Bot) Reply added in v0.3.0

func (b *Bot) Reply(ctx context.Context, m *Message, text string) error

Reply is convenience sugar for replying into the thread or reply-chain of the inbound message m — it is exactly SendMessageContext(ctx, m.ChannelID, text, InReplyTo(m)). Each adapter derives its own platform-specific anchor; see SendOptions. It returns ErrNilMessage if m is nil, or ErrUnknownBotType if the Bot has no adapter.

func (*Bot) ResolveAttachmentURL added in v0.2.0

func (b *Bot) ResolveAttachmentURL(ctx context.Context, att Attachment) (string, error)

ResolveAttachmentURL returns a downloadable URL for att. If the adapter implements AttachmentResolver the call is delegated; otherwise att.URL is returned verbatim. It returns ErrUnknownBotType if the Bot has no adapter. An empty string with a nil error means "not resolvable", not a failure.

The result is consumed differently per platform:

  • Discord: a signed CDN link (~24h); plain GET, consume promptly.
  • Slack: not directly fetchable — download via the Slack Web API client (SlackClient(b).GetFileContext), which injects the bot token.
  • Telegram: a plain GET on a secret, ~1h URL that embeds the bot token — never log or cache it. Each resolve logs a warning, suppressible via BOTBOOTER_TELEGRAM_SUPPRESS_URL_WARNING.
  • WhatsApp: GET with an "Authorization: Bearer <token>" header (the Cloud API token used to send). Short-lived; consume promptly.
  • Teams: a pre-authorized link carrying a short-lived token — consume promptly, never log or cache. Inline images may need an Authorization header this adapter does not yet supply.
  • CLI: a local filesystem path (open with os.Open), not an HTTP URL.

func (*Bot) Run

func (b *Bot) Run(ctx context.Context) error

Run connects the Bot and blocks until ctx is canceled, the event loop ends, or Disconnect is called from elsewhere, then disconnects. A clean shutdown (ctx cancellation or a local Disconnect) returns nil rather than ctx.Err(), so callers can safely do log.Fatal(bot.Run(ctx)).

func (*Bot) SendMessage

func (b *Bot) SendMessage(channelID, text string, opts ...SendOption) error

SendMessage sends text to channelID using a background context. Prefer SendMessageContext from within a handler so the send honors shutdown and cancellation; SendMessage's background context outlives Run's teardown.

func (*Bot) SendMessageContext

func (b *Bot) SendMessageContext(ctx context.Context, channelID, text string, opts ...SendOption) error

SendMessageContext sends text to channelID, honoring ctx for cancellation. Pass InReplyTo or WithThreadID to thread the message onto an inbound one; with no options it is a plain channel message.

func (*Bot) SetLogger added in v0.3.0

func (b *Bot) SetLogger(logger *slog.Logger)

SetLogger routes the Bot's and its adapter's diagnostics (panic recovery, shutdown warnings, webhook rejections) through logger instead of slog.Default. Like handler registration, call it before Connect.

func (*Bot) SetUnknownCommandHandler

func (b *Bot) SetUnknownCommandHandler(handler CommandHandler)

SetUnknownCommandHandler sets the handler invoked when a message matches no registered command; if unset, unmatched messages are ignored.

func (*Bot) Start

func (b *Bot) Start() error

Start runs the Bot until the process receives an interrupt or SIGTERM.

type BotType

type BotType int

BotType identifies the messaging platform a Bot is connected to.

const (
	SlackBotType BotType = iota
	DiscordBotType
	CLIBotType
	TelegramBotType
	WhatsAppBotType
	TeamsBotType
)

The supported bot types.

func (BotType) String

func (t BotType) String() string

type CLIMessage

type CLIMessage struct {
	Text        string
	Attachments []Attachment
}

CLIMessage is the raw payload of a message read from the CLI adapter.

type Command

type Command struct {
	Pattern string
	Handler CommandHandler
	// contains filtered or unexported fields
}

Command pairs a regular-expression Pattern with the Handler to run on a match.

type CommandHandler

type CommandHandler func(ctx context.Context, b *Bot, m *Message)

CommandHandler handles a dispatched message for a matched command.

type Message

type Message struct {
	ID               string
	UserID           string
	AuthorName       string
	ChannelID        string
	Content          string
	Timestamp        time.Time
	ReplyToID        string
	MentionedUserIDs []string

	Raw any
}

Message is a platform-agnostic incoming message handed to command handlers. UserID, ChannelID and Content are always set; the remaining normalized fields are best-effort per platform. Raw carries the originating platform's untouched event; read it with the matching typed accessor (e.g. discord.RawEvent).

type Middleware

type Middleware func(ctx context.Context, b *Bot, m *Message, next CommandHandler)

Middleware wraps message dispatch; it must call next to continue the chain.

type SendOption added in v0.3.0

type SendOption func(*SendOptions)

SendOption modifies a SendOptions. Construct them with InReplyTo / WithThreadID and pass them to Bot.SendMessageContext / Bot.SendMessage.

func InReplyTo added in v0.3.0

func InReplyTo(m *Message) SendOption

InReplyTo anchors the send on m so the adapter posts into m's thread or reply-chain, deriving the correct per-platform anchor itself.

func WithThreadID added in v0.3.0

func WithThreadID(id string) SendOption

WithThreadID anchors the send on a raw native id the adapter uses verbatim. It takes precedence over InReplyTo. The caller owns platform-correctness.

type SendOptions added in v0.3.0

type SendOptions struct {
	ReplyTo  *Message
	ThreadID string
}

SendOptions is the resolved set of per-send modifiers an Adapter reads off a Send call. Its zero value means "a plain channel message". A threading anchor is platform-specific, so each adapter derives its own from these fields:

  • ReplyTo: reply anchored on this whole message; the adapter picks the correct native anchor (Slack thread_ts from ReplyToID; Discord/Telegram/ WhatsApp the replied-to/quoted message id).
  • ThreadID: a raw native anchor supplied by the caller, used verbatim. It wins over ReplyTo when both are set. On Slack it is a thread_ts; on Discord/Telegram/WhatsApp a reply/quote message id (NOT a Discord thread-channel id).

Jump to

Keyboard shortcuts

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