TelegramEx.FSM (TelegramEx v1.2.1)

Copy Markdown View Source

Per-user Finite State Machine backed by Pockets (ETS).

This module provides per-user state management for conversational bots. Each user (identified by chat ID) can have their own state and associated data, allowing you to build multi-step workflows, wizards, and stateful interactions.

The FSM stores tuples of {state, data} keyed by chat ID in an ETS table managed by the Pockets library.

State Management

  • State: An atom representing the current conversation state (e.g., :waiting_name, :confirming)
  • Data: Any Elixir term to store context (e.g., %{step: 1, answers: []})

Usage

# Get current state and data
{state, data} = FSM.get_state(:my_bot, chat_id)

# Set state only (keeps existing data)
FSM.set_state(:my_bot, chat_id, :waiting_name)

# Set state and data
FSM.set_state(:my_bot, chat_id, :waiting_name, %{step: 1})

# Reset state (removes entry)
FSM.reset_state(:my_bot, chat_id)

Using with defstate

The defstate/2 macro allows you to define handlers that only match when the user is in a specific state:

defstate :waiting_name do
  def handle_message(%{text: text, chat: chat}, ctx) do
    # This only runs when user is in :waiting_name state
    {:transition, :waiting_age, Map.put(ctx.data, :name, text)}
  end
end

Summary

Functions

Defines handlers that only execute when the user is in a specific FSM state.

Retrieves the current FSM state and data for a user.

Initializes the FSM storage for a bot.

Resets the FSM state for a user, removing their stored state and data.

Sets the FSM state for a user, keeping existing data.

Sets the FSM state and data for a user.

Types

chat_id()

@type chat_id() :: TelegramEx.Types.chat_id()

Functions

defstate(state, list)

(macro)

Defines handlers that only execute when the user is in a specific FSM state.

This macro automatically injects state pattern matching into handler functions, so they only run when the user's FSM state matches the specified state.

Parameters

  • state - The state atom to match
  • do - Block containing handler function definitions

Examples

defstate :waiting_name do
  def handle_message(%{text: text, chat: chat}, ctx) do
    # Only runs when user is in :waiting_name state
    ctx
    |> Message.text("Got your name: #{text}")
    |> Message.send(chat["id"])

    {:transition, :waiting_age, Map.put(ctx.data, :name, text)}
  end
end

defstate :waiting_age do
  def handle_message(%{text: age, chat: chat}, ctx) do
    # Only runs when user is in :waiting_age state
    name = ctx.data.name

    ctx
    |> Message.text("Hello #{name}, you are #{age} years old")
    |> Message.send(chat["id"])

    FSM.reset_state(:my_bot, chat["id"])
  end
end

Note

The ctx parameter in handlers defined within defstate will have ctx.state automatically matched to the specified state atom.

get_state(name, id)

@spec get_state(atom(), chat_id()) :: {term(), term()}

Retrieves the current FSM state and data for a user.

Parameters

  • name - The bot name (atom)
  • id - The chat ID

Returns

A tuple {state, data} where:

  • state - Current state atom (or nil if no state set)
  • data - Associated data (or nil if no data set)

Examples

{state, data} = FSM.get_state(:my_bot, 123456)
# => {:waiting_name, %{step: 1}}

{state, data} = FSM.get_state(:my_bot, 999999)
# => {nil, nil}  # User has no state

init(name)

@spec init(atom()) :: :ok | {:error, term()}

Initializes the FSM storage for a bot.

This is called automatically by TelegramEx.Server when the bot starts. You typically don't need to call this manually.

Parameters

  • name - The bot name (atom)

Returns

  • :ok - FSM initialized successfully
  • {:error, reason} - Initialization failed

reset_state(name, id)

@spec reset_state(atom(), chat_id()) :: atom() | {:error, term()}

Resets the FSM state for a user, removing their stored state and data.

Parameters

  • name - The bot name (atom)
  • id - The chat ID

Returns

  • :ok - State reset successfully
  • {:error, reason} - Failed to reset state

Examples

FSM.reset_state(:my_bot, 123456)
# User's state is now cleared

set_state(name, id, state)

@spec set_state(atom(), chat_id(), atom()) :: atom() | {:error, term()}

Sets the FSM state for a user, keeping existing data.

Parameters

  • name - The bot name (atom)
  • id - The chat ID
  • state - New state atom

Returns

  • :ok - State set successfully
  • {:error, reason} - Failed to set state

Examples

FSM.set_state(:my_bot, 123456, :waiting_age)
# State changed to :waiting_age, data preserved

set_state(name, id, state, data)

@spec set_state(atom(), chat_id(), atom(), term()) :: atom() | {:error, term()}

Sets the FSM state and data for a user.

Parameters

  • name - The bot name (atom)
  • id - The chat ID
  • state - New state atom
  • data - New data to store

Returns

  • :ok - State and data set successfully
  • {:error, reason} - Failed to set state

Examples

FSM.set_state(:my_bot, 123456, :waiting_age, %{name: "John", step: 2})
# State and data both updated