Telegram.ChatBot behaviour (telegram v2.1.0)

View Source

Telegram Chat Bot behaviour.

The Telegram.ChatBot module provides a stateful chatbot mechanism that manages bot instances on a per-chat basis (chat_id). Unlike the Telegram.Bot behavior, which is stateless, each conversation in Telegram.ChatBot is tied to a unique chat_state.

The get_chat/2 callback is responsible for routing each incoming update to the correct chat session by returning the chat's identifier. If the chat is not yet recognized, a new bot instance will automatically be created for that chat.

Since each conversation is handled by a long-running process, it's crucial to manage session timeouts carefully. Without implementing timeouts, your bot may hit the max_bot_concurrency limit, preventing it from handling new conversations. To prevent this, you can utilize the underlying :gen_server timeout mechanism by specifying timeouts in the return values of the init/1 or handle_update/3 callbacks. Alternatively, for more complex scenarios, you can manage explicit timers in your bot's logic.

Example

defmodule HelloBot do
  use Telegram.ChatBot

  # Session timeout set to 60 seconds
  @session_ttl 60 * 1_000

  @impl Telegram.ChatBot
  def init(_chat) do
    # Initialize state with a message counter set to 0
    count_state = 0
    {:ok, count_state, @session_ttl}
  end

  @impl Telegram.ChatBot
  def handle_update(%{"message" => %{"chat" => %{"id" => chat_id}}}, token, count_state) do
    # Increment the message count
    count_state = count_state + 1

    Telegram.Api.request(token, "sendMessage",
      chat_id: chat_id,
      text: "Hey! You sent me #{count_state} messages"
    )

    {:ok, count_state, @session_ttl}
  end

  def handle_update(update, _token, count_state) do
    # Ignore unknown updates and maintain the current state

    {:ok, count_state, @session_ttl}
  end

  @impl Telegram.ChatBot
  def handle_info(msg, _token, _chat_id, count_state) do
    # Handle direct erlang messages, if needed

    {:ok, count_state}
  end

  @impl Telegram.ChatBot
  def handle_timeout(token, chat_id, count_state) do
    # Send a "goodbye" message upon session timeout
    Telegram.Api.request(token, "sendMessage",
      chat_id: chat_id,
      text: "See you!"
    )

    {:stop, count_state}
  end
end

Summary

Callbacks

Allows a chatbot to customize how incoming updates are processed.

Invoked to handle arbitrary erlang messages (e.g., scheduled events or direct messages).

Invoked when a chat session is resumed.

Callback invoked when a session times out.

Handles incoming Telegram update events and processes them based on the current chat_state.

Invoked when a chat session is first initialized. Returns the initial chat_state for the session.

Functions

Retrieves the process ID (pid) of a specific chat session.

Types

chat()

@type chat() :: map()

chat_state()

@type chat_state() :: any()

t()

@type t() :: module()

Callbacks

get_chat(update_type, update)

(optional)
@callback get_chat(update_type :: String.t(), update :: Telegram.Types.update()) ::
  {:ok, Telegram.ChatBot.Chat.t()} | :ignore

Allows a chatbot to customize how incoming updates are processed.

This function receives an update and either returns the unique chat identifier associated with it or instructs the bot to ignore the update.

Parameters:

  • update_type: is a string representing the type of update received. For example:
    • message: For new messages.
    • edited_message: For edited messages.
    • inline_query: For inline queries.
  • update: the update object received, containing the data associated with the update_type. The object structure depends on the type of update:
    • For message and edited_message updates, the object is of type Message, which contains fields such as text, sender, and chat.
    • For inline_query updates, the object is of type InlineQuery, containing fields like query and from.

Refer to the official Telegram Bot API documentation for a complete list of update types.

Return values:

  • Returning {:ok, %Telegram.ChatBot.Chat{id: id, metadata: %{}}} will trigger the bot to spin up a new instance, which will manage the update as a full chat session. The instance will be uniquely identified by the return id and init/1 will be called with the returned Telegram.ChatBot.Chat.t/0 struct.
  • Returning :ignore will cause the update to be disregarded entirely.

This callback is optional. If not implemented, the bot will dispatch updates of type Message.

handle_info(msg, token, chat_id, chat_state)

(optional)
@callback handle_info(
  msg :: any(),
  token :: Telegram.Types.token(),
  chat_id :: String.t(),
  chat_state :: chat_state()
) ::
  {:ok, next_chat_state :: chat_state()}
  | {:ok, next_chat_state :: chat_state(), timeout :: timeout()}
  | {:stop, next_chat_state :: chat_state()}

Invoked to handle arbitrary erlang messages (e.g., scheduled events or direct messages).

This callback can be used for:

  • Scheduled Events: handle messages triggered by Process.send/3 or Process.send_after/4.
  • Direct Interactions: respond to direct messages sent to a specific chat session retrieved via lookup/2.

Parameters:

  • msg: the message received.
  • token: the bot's authentication token, used to make API requests.
  • chat_id: the ID of the chat session associated with the message.
  • chat_state: the current state of the chat session.

Return values:

  • {:ok, next_chat_state}: updates the session with a new next_chat_state.
  • {:ok, next_chat_state, timeout}: updates the next_chat_state and sets a new timeout.
  • {:stop, next_chat_state}: terminates the session and returns the final chat_state.

This callback is optional. If not implemented, any received message will be logged by default.

handle_resume(chat_state)

(optional)
@callback handle_resume(chat_state :: chat_state()) ::
  {:ok, next_chat_state :: chat_state()}
  | {:ok, next_chat_state :: chat_state(), timeout :: timeout()}

Invoked when a chat session is resumed.

If implemented, this function allows custom logic when resuming a session, for example, updating the state or setting a new timeout.

Note: you can manually resume a session by calling MyChatBot.resume(token, chat_id, state).

Return values

  • {:ok, next_chat_state}: resumes the session with the provided next_chat_state.
  • {:ok, next_chat_state, timeout}: resumes the session with the next_chat_state and sets a new timeout.

The timeout can be used to schedule actions after a specific period of inactivity.

handle_timeout(token, chat_id, chat_state)

(optional)
@callback handle_timeout(
  token :: Telegram.Types.token(),
  chat_id :: String.t(),
  chat_state :: chat_state()
) ::
  {:ok, next_chat_state :: chat_state()}
  | {:ok, next_chat_state :: chat_state(), timeout :: timeout()}
  | {:stop, next_chat_state :: chat_state()}

Callback invoked when a session times out.

Parameters

  • token: the bot's authentication token, used for making API requests.
  • chat_id: the ID of the chat where the session timed out.
  • chat_state: the current state of the chat session at the time of the timeout.

Return Values:

  • {:ok, next_chat_state}: keeps the session alive with an updated next_chat_state.
  • {:ok, next_chat_state, timeout}: updates the next_chat_state and sets a new timeout.
  • {:stop, next_chat_state}: terminates the session and finalizes the chat_state.

This callback is optional. If not implemented, the bot will stops when a timeout occurs.

handle_update(update, token, chat_state)

@callback handle_update(
  update :: Telegram.Types.update(),
  token :: Telegram.Types.token(),
  chat_state :: chat_state()
) ::
  {:ok, next_chat_state :: chat_state()}
  | {:ok, next_chat_state :: chat_state(), timeout :: timeout()}
  | {:stop, next_chat_state :: chat_state()}

Handles incoming Telegram update events and processes them based on the current chat_state.

Parameters:

  • update: the incoming Telegram update event (e.g., a message, an inline query).
  • token: the bot's authentication token, used to make API requests.
  • chat_state: the current state of the chat session.

Return values:

  • {:ok, next_chat_state}: updates the chat session with the new next_chat_state.
  • {:ok, next_chat_state, timeout}: updates the next_chat_state and sets a new timeout for the session.
  • {:stop, next_chat_state}: terminates the chat session and returns the final next_chat_state.

The timeout option can be used to define how long the bot will wait for the next event before triggering a timeout.

init(chat)

@callback init(chat :: Telegram.ChatBot.Chat.t()) ::
  {:ok, initial_state :: chat_state()}
  | {:ok, initial_state :: chat_state(), timeout :: timeout()}

Invoked when a chat session is first initialized. Returns the initial chat_state for the session.

Parameters:

Return values

  • {:ok, initial_state}: initializes the session with the provided initial_state.
  • {:ok, initial_state, timeout}: initializes the session with the provided initial_state, and sets a timeout for the session.

The timeout can be used to schedule actions after a certain period of inactivity.

Functions

lookup(token, chat_id)

@spec lookup(Telegram.Types.token(), String.t()) ::
  {:error, :not_found} | {:ok, pid()}

Retrieves the process ID (pid) of a specific chat session.

This function allows you to look up the active process managing a particular chat session.

Note: it is the user's responsibility to maintain and manage the mapping between the custom session identifier (specific to the business logic) and the Telegram chat_id.

Return values:

  • {:ok, pid}: successfully found the pid of the chat session.
  • {:error, :not_found}: no active session was found for the provided chat_id.