teleflow

package module
v1.0.0-beta.2 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 10 Imported by: 0

README

teleflow

Go Version Go Reference Tests codecov License

go get github.com/hellocashmere/teleflow

Overview

Teleflow is a package which is designed to solve the problem of dialogs using Telebot. It provides a simple and efficient way to receive and process both input text and is capable of handling button clicks, location sending, contacts, etc.

Getting Started

The following bot collects a destination and traveler count in two steps:

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/hellocashmere/teleflow"
	"github.com/hellocashmere/teleflow/storage"
	tele "gopkg.in/telebot.v4"
)

func main() {
	pref := tele.Settings{
		Token:  os.Getenv("TOKEN"),
		Poller: &tele.LongPoller{Timeout: 10 * time.Second},
	}

	bot, err := tele.NewBot(pref)
	if err != nil {
		log.Fatal(err)
	}

	bus := teleflow.NewBus(storage.NewMemory())

	bot.Handle(tele.OnText, bus.Handle)
	bot.Handle("/trip", TripHandler(bus))

	log.Println("telegram bot started successfully")
	bot.Start()
}

func TripHandler(bus teleflow.Bus) tele.HandlerFunc {
	trip := bus.NewFlow(teleflow.FlowConfig{
		Name:        "trip",
		Version:     1,
		IdleTimeout: 15 * time.Minute,
	})

	trip.Step(
		"destination",
		tele.OnText,
		func(c tele.Context) error {
			return c.Send("Where do you want to go?")
		},
		func(c tele.Context, fc teleflow.Context) (teleflow.StepResult, error) {
			destination := strings.TrimSpace(c.Text())
			if destination == "" {
				return fc.Stay(), c.Send("Please enter a destination.")
			}

			if err := fc.SetString("destination", destination); err != nil {
				return fc.Stay(), err
			}

			return fc.Next(), nil
		},
	)

	trip.Step(
		"travelers",
		tele.OnText,
		func(c tele.Context) error {
			return c.Send("How many people are traveling?")
		},
		func(c tele.Context, fc teleflow.Context) (teleflow.StepResult, error) {
			travelers, err := strconv.Atoi(strings.TrimSpace(c.Text()))
			if err != nil || travelers < 1 {
				return fc.Stay(), c.Send("Please enter a positive whole number.")
			}

			if err := fc.SetInt("travelers", travelers); err != nil {
				return fc.Stay(), err
			}

			destination, _ := fc.GetString("destination")
			if err := c.Send(fmt.Sprintf(
				"Trip to %s saved for %d travelers.",
				destination,
				travelers,
			)); err != nil {
				return fc.Stay(), err
			}

			return fc.Next(), nil
		},
	)

	return func(c tele.Context) error {
		if err := trip.Build(); err != nil {
			return fmt.Errorf("build trip flow: %w", err)
		}

		return bus.Start(context.Background(), c, trip)
	}
}

bus.Handle receives updates for active flows. The command handler builds the flow definition and starts a new session. Returning Next from the final step completes the flow and removes its stored state.

Register bus.Handle for every Telebot event used by your steps. For example, an inline-button flow also needs bot.Handle(tele.OnCallback, bus.Handle).

Context

Step callbacks work with two different contexts:

  • tele.Context contains the current Telegram update and methods such as Send, Edit, Text, Message, and Callback.
  • teleflow.Context contains the current flow state, typed data, metadata, and transition methods.

Use teleflow.Context to pass data between steps:

if err := fc.SetString("company", "Acme"); err != nil {
	return fc.Stay(), err
}

company, ok := fc.GetString("company")

Typed methods are available for strings, booleans, integers, floating-point numbers, durations, and times. Delete removes a value, while Has checks whether a key exists.

The same context controls the next transition:

return fc.Stay(), nil
return fc.Next(), nil
return fc.Back(), nil
return fc.Go("company_details"), nil

Current and Step return the active step name. Depth reports the history depth, and CanBack reports whether Back can return to an earlier step. A teleflow.Context belongs to one callback, but the data written through it is persisted with the flow state.

The standard library context.Context is used for cancellation and deadlines. It is passed to Bus.Start, Bus.Cancel, and storage methods. Use Flow.StepContext when step work must observe cancellation:

flow.StepContext(
	"company_details",
	tele.OnText,
	func(ctx context.Context, c tele.Context) error {
		return c.Send("Tell us about your company.")
	},
	func(
		ctx context.Context,
		c tele.Context,
		fc teleflow.Context,
	) (teleflow.StepResult, error) {
		if err := ctx.Err(); err != nil {
			return fc.Stay(), err
		}

		return fc.Next(), nil
	},
)

An omitted IdleTimeout uses teleflow.DefaultIdleTimeout of 24 hours. An omitted HistoryLimit uses teleflow.DefaultHistoryLimit of 64 entries.

License

Teleflow is distributed under the MIT License.

Documentation

Index

Constants

View Source
const (
	// DefaultIdleTimeout is used when FlowConfig.IdleTimeout is zero.
	DefaultIdleTimeout = 24 * time.Hour

	// DefaultHistoryLimit is used when FlowConfig.HistoryLimit is zero.
	DefaultHistoryLimit = 64

	// MaxRuntimeStateSize limits serialized state loaded from or written to storage.
	MaxRuntimeStateSize = 1 << 20
)

Variables

View Source
var (
	ErrNameEmpty               = errors.New("flow: name cannot be empty")
	ErrVersionInvalid          = errors.New("flow: version must be greater than zero")
	ErrIdleTimeoutNegative     = errors.New("flow: idle timeout cannot be negative")
	ErrHistoryLimitNegative    = errors.New("flow: history limit cannot be negative")
	ErrNilDefinition           = errors.New("flow: nil definition")
	ErrNoStepsDefined          = errors.New("flow: no steps defined")
	ErrDefinitionExists        = errors.New("flow: definition already exists")
	ErrExpired                 = errors.New("flow: expired")
	ErrDefinitionNotRegistered = errors.New("flow: definition is not registered")
	ErrInvalidRuntimeState     = errors.New("flow: invalid runtime state")
	ErrContextNoSender         = errors.New("flow: context has no sender")
	ErrInvalidStepResult       = errors.New("flow: invalid step result")
	ErrStorageNil              = errors.New("flow: storage is nil")
	ErrConcurrentUpdate        = errors.New("flow: state changed concurrently")
	ErrStateBusy               = errors.New("flow: state is being processed")
	ErrTargetStepNotFound      = errors.New("flow: target step not found")
)
View Source
var (
	ErrDataKeyEmpty = errors.New("flow: data key cannot be empty")
	ErrInvalidValue = errors.New("flow: invalid value")
)
View Source
var (
	ErrFlowNotBuilt     = errors.New("flow: definition is not built")
	ErrStepNameEmpty    = errors.New("flow: step name cannot be empty")
	ErrStepEventEmpty   = errors.New("flow: step event cannot be empty")
	ErrStepDuplicate    = errors.New("flow: duplicate step")
	ErrStepHandlerNil   = errors.New("flow: step handler cannot be nil")
	ErrRuntimeStateSize = errors.New("flow: runtime state is too large")
)

Functions

This section is empty.

Types

type BeginContextFunc

type BeginContextFunc func(context.Context, tele.Context) error

BeginContextFunc is a context-aware step entry handler.

type BeginFunc

type BeginFunc func(tele.Context) error

BeginFunc is called when a flow enters a step.

type Bus

type Bus interface {
	// NewFlow creates a versioned flow draft.
	NewFlow(config FlowConfig) *Flow

	// Start starts a flow or resumes its pending begin callback.
	Start(ctx context.Context, c tele.Context, flow *Flow) error

	// Handle processes an inferred Telebot event.
	Handle(c tele.Context) error

	// Cancel removes the active flow for the current chat and sender.
	Cancel(ctx context.Context, c tele.Context) error
}

func NewBus

func NewBus(store storage.Storage) Bus

NewBus creates [bus].

type Context

type Context interface {
	// SetString stores a string value under the key.
	SetString(string, string) error

	// SetBool stores a boolean value under the key.
	SetBool(string, bool) error

	// SetInt stores an int value normalized to int64.
	SetInt(string, int) error

	// SetInt32 stores an int32 value normalized to int64.
	SetInt32(string, int32) error

	// SetInt64 stores an int64 value under the key.
	SetInt64(string, int64) error

	// SetFloat32 stores a finite float32 value normalized to float64.
	SetFloat32(string, float32) error

	// SetFloat64 stores a finite float64 value under the key.
	SetFloat64(string, float64) error

	// SetDuration stores a time.Duration as an int64 nanosecond count.
	SetDuration(string, time.Duration) error

	// SetTime stores a time.Time as an RFC3339Nano string.
	SetTime(string, time.Time) error

	// Delete removes the value stored under the key.
	Delete(string) error

	// Has reports whether the key exists in the current flow data.
	Has(string) bool

	// GetString returns the string value stored under the key.
	GetString(string) (string, bool)

	// GetBool returns the boolean value stored under the key.
	GetBool(string) (bool, bool)

	// GetInt returns the stored integer when it fits in an int.
	GetInt(string) (int, bool)

	// GetInt32 returns the stored integer when it fits in an int32.
	GetInt32(string) (int32, bool)

	// GetInt64 returns the stored int64 value.
	GetInt64(string) (int64, bool)

	// GetFloat32 returns the stored floating-point value when it fits in a float32.
	GetFloat32(string) (float32, bool)

	// GetFloat64 returns the stored float64 value.
	GetFloat64(string) (float64, bool)

	// GetDuration returns the stored duration.
	GetDuration(string) (time.Duration, bool)

	// GetTime returns the stored time.Time value.
	GetTime(string) (time.Time, bool)

	// Current returns the name of the current step.
	Current() string

	// Depth returns the number of step entries currently present in history.
	Depth() int

	// CanBack reports whether the flow can move to a previous step.
	CanBack() bool

	// Step returns the name of the currently active step.
	Step() string

	// Next advances the flow to the next defined step after the handler returns.
	Next() StepResult

	// Back moves the flow to the previous step when history allows it.
	Back() StepResult

	// Go moves the flow to the named step after the handler returns.
	Go(string) StepResult

	// Stay keeps the flow on the current step after the handler returns.
	Stay() StepResult
}

type Flow

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

Flow describes a registered flow definition and its ordered steps.

func (*Flow) Build

func (f *Flow) Build() error

Build validates and registers the immutable flow definition.

func (*Flow) Name

func (f *Flow) Name() string

Name returns the flow definition name.

func (*Flow) Step

func (f *Flow) Step(
	name string,
	event string,
	begin BeginFunc,
	handle HandleFunc,
)

Step adds a named step to the flow draft.

func (*Flow) StepContext

func (f *Flow) StepContext(
	name string,
	event string,
	begin BeginContextFunc,
	handle HandleContextFunc,
)

StepContext adds a context-aware named step to the flow draft.

func (*Flow) Version

func (f *Flow) Version() uint

Version returns the flow definition version.

type FlowConfig

type FlowConfig struct {
	// Name identifies the flow and must not be empty.
	Name string

	// Version identifies the flow definition and must be greater than zero.
	Version uint

	// IdleTimeout sets when inactive state expires and uses DefaultIdleTimeout when zero.
	IdleTimeout time.Duration

	// HistoryLimit sets the retained step count and uses DefaultHistoryLimit when zero.
	HistoryLimit int
}

FlowConfig is used to configure a flow.

type HandleContextFunc

type HandleContextFunc func(context.Context, tele.Context, Context) (StepResult, error)

HandleContextFunc is a context-aware step event handler.

type HandleFunc

type HandleFunc func(tele.Context, Context) (StepResult, error)

HandleFunc processes an event received by the current step.

type Step

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

func (*Step) Event

func (s *Step) Event() string

Event returns the Telebot event type handled by the step.

func (*Step) Name

func (s *Step) Name() string

Name returns the stable identifier of the step.

type StepResult

type StepResult uint8

StepResult describes the transition requested by a step handler.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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