Hotline.Flow.Engine (Hotline v0.2.1)

Copy Markdown View Source

GenServer that manages active conversation flows per chat.

Subscribes to PubSub updates and routes them to the correct flow. Sends prompts and error messages back via the Telegram API.

Supervision Tree

Add the Engine before any bots that use flows:

children = [
  {Hotline.Poller, []},
  {Hotline.Flow.Engine, []},
  {MyBot, []}
]

Starting Flows

# From a Bot's handle_update/2:
def handle_update(%{message: %{text: "/register", chat: %{id: chat_id}}}, state) do
  Hotline.Flow.Engine.start_flow(chat_id, MyBot.Flows.Registration)
  {:noreply, state}
end

Integration with Bots

Use handles_update?/1 to skip updates being handled by an active flow:

def handle_update(update, state) do
  unless Hotline.Flow.Engine.handles_update?(update) do
    # normal handling
  end
  {:noreply, state}
end

Testing

Inject a :sender function to capture messages in tests:

test_pid = self()
sender = fn params, _opts -> send(test_pid, {:sent, params}); {:ok, %{}} end
{:ok, engine} = Engine.start_link(sender: sender, name: :test_engine)

Summary

Functions

Check if a chat has an active flow.

Cancel the active flow for a chat.

Returns a specification to start this module under a supervisor.

Check if an update belongs to a chat with an active flow.

Functions

active_flow?(chat_id, engine \\ __MODULE__)

@spec active_flow?(integer(), GenServer.server()) :: boolean()

Check if a chat has an active flow.

cancel_flow(chat_id, engine \\ __MODULE__)

@spec cancel_flow(integer(), GenServer.server()) :: :ok | {:error, :no_flow}

Cancel the active flow for a chat.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

handles_update?(update, engine \\ __MODULE__)

@spec handles_update?(map(), GenServer.server()) :: boolean()

Check if an update belongs to a chat with an active flow.

start_flow(chat_id, flow_module, opts \\ %{}, engine \\ __MODULE__)

@spec start_flow(integer(), module(), map(), GenServer.server()) ::
  :ok | {:error, :flow_active | :flow_start_failed}

Start a flow for a chat.

Returns {:error, :flow_active} if one is already running, or {:error, :flow_start_failed} if the flow_module raises while starting (e.g. a module that does not use Hotline.Flow). A failed start never crashes the Engine, so other chats' in-flight flows are unaffected.

start_link(opts \\ [])