botbooter

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 26, 2026 License: MIT Imports: 5 Imported by: 0

README

botbooter

Go Reference CI Go Report Card Go Version

A small, framework-style toolkit for writing chat bots once and running them on Slack, Discord, or a local CLI — with the same handlers, middleware, and attachment access on every platform.

Inspired by Gin: you register pattern-matched command handlers and optional middleware, then run the bot. botbooter abstracts the platform behind a single Bot type so your business logic does not care whether a message came from Slack, Discord, or stdin.

⚠️ Pre-1.0 — the public API may still change.

Features

  • One API, multiple platforms — Slack (Socket Mode), Discord (Gateway), and a built-in CLI adapter for local development and testing with no credentials.
  • Regex command routing — patterns are compiled once and matched against message content; first match wins.
  • Middleware chain — wrap every message (logging, auth, metrics, …) with next-style composition.
  • Platform-agnostic attachments — read image/file attachments uniformly across platforms.
  • Context-first & graceful shutdown — handlers receive a context.Context; Run(ctx) / Start() connect and shut down cleanly on cancellation or SIGINT/SIGTERM.
  • Resilient dispatch — a panicking handler is recovered and logged instead of taking down the bot.

Install

go get github.com/lao/botbooter

Requires Go 1.23+.

Quickstart

The fastest way to try it — the CLI adapter needs no tokens:

package main

import (
	"context"
	"os"
	"strings"

	"github.com/lao/botbooter"
)

func main() {
	bot := botbooter.InitAsCLIBot(os.Stdin, os.Stdout)

	_ = bot.HandleFunc("^echo ", func(ctx context.Context, b *botbooter.Bot, m *botbooter.Message) {
		_ = b.SendMessageContext(ctx, m.ChannelID, strings.TrimPrefix(m.Content, "echo "))
	})

	ctx, stop := context.WithCancel(context.Background())
	defer stop()
	_ = bot.Run(ctx) // type "echo hi", press enter; Ctrl-D to quit
}

Or run the bundled example directly:

go run ./examples/v1            # CLI mode (default, no credentials)
go run ./examples/v1 slack      # uses SLACK_APP_TOKEN / SLACK_BOT_TOKEN
go run ./examples/v1 discord    # uses DISCORD_BOT_TOKEN

Concepts

Constructing a bot
Constructor Signature Notes
InitAsCLIBot(in io.Reader, out io.Writer) *Bot Local adapter; nil defaults to stdin/stdout.
InitAsSlackBot(appToken, botToken string) *Bot Socket Mode (xapp-… + xoxb-…).
InitAsDiscordBot(token string) (*Bot, error) Enables the message-content intent (see below).
Handlers, commands and middleware
// A command routes messages whose content matches a regular expression.
_ = bot.AddHandler(botbooter.Command{
	Pattern: "^ping$",
	Handler: func(ctx context.Context, b *botbooter.Bot, m *botbooter.Message) {
		_ = b.SendMessageContext(ctx, m.ChannelID, "pong")
	},
})

// HandleFunc is a shorthand for the common case.
_ = bot.HandleFunc("^hello", greetHandler)

// Fallback when nothing matches.
bot.SetUnknownCommandHandler(func(ctx context.Context, b *botbooter.Bot, m *botbooter.Message) {
	_ = b.SendMessageContext(ctx, m.ChannelID, "unknown command")
})

// Middleware wraps dispatch; call next to continue the chain.
bot.AddMiddleware(func(ctx context.Context, b *botbooter.Bot, m *botbooter.Message, next botbooter.CommandHandler) {
	log.Printf("%s in %s: %s", m.UserID, m.ChannelID, m.Content)
	next(ctx, b, m)
})

AddHandler / HandleFunc return an error if the pattern is not a valid regular expression.

Attachments
attachments, err := b.GetAttachments(m)
for _, a := range attachments {
	fmt.Println(a.URL, a.IsImage) // a.ExtraData holds the raw platform payload
}

A terminal has no real upload channel, so the CLI adapter treats any local file path in the message as an attachment — "uploading" means referencing the path. Image files are detected by content sniffing:

echo here is my screenshot /tmp/cat.png
  → attachment (image): /tmp/cat.png
Lifecycle
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

if err := bot.Run(ctx); err != nil { // connect, serve, and shut down on cancel
	log.Fatal(err)
}
  • Run(ctx) — connect, block until ctx is canceled (or the event loop ends), then disconnect cleanly.
  • Start() — shorthand for Run with a context bound to SIGINT/SIGTERM.
  • Connect(ctx) / Disconnect() — non-blocking control if you want to manage the loop yourself. Disconnect is idempotent.

Platform setup

Each platform takes different credentials. Full step-by-step setup, troubleshooting, and the official documentation for each live in docs/platforms.md.

Platform What you need Setup
Slack xapp-… app-level token + xoxb-… bot token docs/platforms.md
Discord bot token + Message Content Intent docs/platforms.md
CLI nothing (local stdin/stdout) docs/platforms.md

Development

make all        # fmt + vet + lint + test
make test-race  # race detector
make cover      # coverage report
make run-cli    # run the example bot in CLI mode

The suite runs under the race detector and is hermetic by default. The single test that touches the Slack network is opt-in, enabled by setting the BOTBOOTER_SLACK_NETWORK_TEST environment variable (see botbooter_test.go).

DEMO

for slack and discord:

https://user-images.githubusercontent.com/197033/229368894-19b366d3-ca6d-41d2-9ab7-ca8e1a53b31a.mov

Why

Alternatives:

Joe-bot
  • no support for Discord
  • no generic access for attachments in messages
GoSarah
  • no support for Discord
  • no generic access for attachments in messages

Roadmap

  • Slack, Discord and CLI adapters
  • Middleware and attachment abstraction
  • Microsoft Teams, Telegram, WhatsApp adapters
  • Richer message types (blocks, embeds)

Contributing

Issues and PRs are welcome. Please run make all (format, vet, lint, race tests) before opening a PR.

License

MIT © Lucas Abreu Oliveira

Documentation

Overview

Package botbooter is a small framework for building chat bots that behave the same way across Slack, Discord and a local CLI. A single Bot abstracts over the platforms; you register Command handlers and optional Middleware, then run the bot.

This package is a thin facade over the implementation in the internal packages, so that consumers keep a single import path.

Example

Example shows the CLI adapter, which needs no external credentials: it reads messages from an io.Reader and writes replies to an io.Writer.

package main

import (
	"context"
	"os"
	"strings"

	"github.com/lao/botbooter"
)

func main() {
	bot := botbooter.InitAsCLIBot(strings.NewReader("echo hello\n"), os.Stdout)

	_ = bot.HandleFunc("^echo ", func(ctx context.Context, b *botbooter.Bot, m *botbooter.Message) {
		_ = b.SendMessageContext(ctx, m.ChannelID, strings.TrimPrefix(m.Content, "echo "))
	})

	// Run returns when the input reaches EOF.
	_ = bot.Run(context.Background())
}
Output:
hello

Index

Examples

Constants

View Source
const (
	SlackBotType   = core.SlackBotType
	DiscordBotType = core.DiscordBotType
	CLIBotType     = core.CLIBotType
)

Supported bot types.

Variables

View Source
var (
	ErrUnknownBotType   = core.ErrUnknownBotType
	ErrAlreadyConnected = core.ErrAlreadyConnected
)

Errors returned by Bot methods.

Functions

This section is empty.

Types

type Attachment

type Attachment = core.Attachment

Attachment is a platform-agnostic file attachment. See core.Attachment.

type Bot

type Bot = core.Bot

Bot is the platform-agnostic chat bot. See core.Bot.

func InitAsCLIBot

func InitAsCLIBot(in io.Reader, out io.Writer) *Bot

InitAsCLIBot creates a bot that reads newline-delimited messages from in and writes replies to out. When in or out is nil, os.Stdin and os.Stdout are used respectively. It is intended for trusted, local input only.

func InitAsDiscordBot

func InitAsDiscordBot(token string) (*Bot, error)

InitAsDiscordBot creates a Discord bot from a bot token. It returns an error if the token cannot be used to construct a session.

func InitAsSlackBot

func InitAsSlackBot(appToken, botToken string) *Bot

InitAsSlackBot creates a Slack bot that connects via Socket Mode. appToken is the app-level token (xapp-...) and botToken is the bot token (xoxb-...).

type BotType

type BotType = core.BotType

BotType identifies the messaging platform a Bot is connected to.

type CLIMessage

type CLIMessage = core.CLIMessage

CLIMessage is the raw payload of a CLI message. See core.CLIMessage.

type Command

type Command = core.Command

Command pairs a regexp pattern with a handler. See core.Command.

type CommandHandler

type CommandHandler = core.CommandHandler

CommandHandler handles a matched message. See core.CommandHandler.

type Message

type Message = core.Message

Message is an incoming message handed to handlers. See core.Message.

type Middleware

type Middleware = core.Middleware

Middleware wraps message dispatch. See core.Middleware.

Directories

Path Synopsis
examples
v1 command
Command v1 is a small demo of botbooter.
Command v1 is a small demo of botbooter.
internal
asserts
Package asserts holds tiny test assertion helpers shared across botbooter's test packages.
Package asserts holds tiny test assertion helpers shared across botbooter's test packages.
cli
Package cli is the local CLI adapter for botbooter.
Package cli is the local CLI adapter for botbooter.
core
Package core holds botbooter's platform-agnostic engine: the Bot type, its command/middleware dispatch, and the connection lifecycle.
Package core holds botbooter's platform-agnostic engine: the Bot type, its command/middleware dispatch, and the connection lifecycle.
discord
Package discord is the Discord adapter for botbooter.
Package discord is the Discord adapter for botbooter.
slack
Package slack is the Slack adapter for botbooter.
Package slack is the Slack adapter for botbooter.

Jump to

Keyboard shortcuts

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