telega/bot

Core bot actor and chat instance management.

This module implements the actor-based architecture for handling Telegram updates. It contains the Bot actor (the central dispatcher) and ChatInstance actors (one per unique {chat_id}:{from_id} combination).

Supervision tree

Both the Bot actor and ChatInstance actors run inside a supervision tree created by telega.init() or telega.init_for_polling():

TelegaRootSupervisor (static_supervisor, OneForOne)
├── ChatInstances (factory_supervisor, Transient children)
│   ├── ChatInstance {chat1:user1}
│   ├── ChatInstance {chat2:user2}
│   └── ...
├── Bot actor (worker, Permanent)
└── Polling worker (worker, Permanent) — only for polling mode

Handler pattern

All handlers follow this signature:

fn handler(ctx: Context(session, error, dependencies), data: Type) -> Result(Context(session, error, dependencies), error)

Always return the updated context — it carries the (potentially modified) session.

Conversation API

The wait_handler function and the Handler type enable multi-message conversations: the chat instance suspends its main handler and waits for a specific update type. See telega.wait_text, telega.wait_command, etc.

Types

Stores information about running bot instance

pub opaque type Bot(session, error, dependencies)
pub opaque type BotMessage
pub type CallbackQueryFilter {
  CallbackQueryFilter(re: regexp.Regexp)
}

Constructors

Handler called when an error occurs in handler If handler returns Error, the bot will be stopped and the error will be logged The default handler is fn(_) -> Ok(Nil), which will do nothing if handler returns an error

pub type CatchHandler(session, error, dependencies) =
  fn(Context(session, error, dependencies), error) -> Result(
    Nil,
    error,
  )

Arguments for starting a chat instance via factory supervisor.

pub type ChatInstanceArgs(session, error, dependencies) {
  ChatInstanceArgs(
    key: String,
    config: @internal Config,
    session_settings: SessionSettings(session, error),
    catch_handler: fn(
      Context(session, error, dependencies),
      error,
    ) -> Result(Nil, error),
    dependencies: dependencies,
    router_handler: fn(
      Context(session, error, dependencies),
      update.Update,
    ) -> Result(Context(session, error, dependencies), error),
    bot_info: types.User,
    registry: @internal Registry(
      ChatInstanceMessage(session, error, dependencies),
    ),
    bot_subject: process.Subject(BotMessage),
  )
}

Constructors

pub opaque type ChatInstanceMessage(session, error, dependencies)
pub type ChatInstanceSubject(session, error, dependencies) =
  process.Subject(
    ChatInstanceMessage(session, error, dependencies),
  )

Context holds information needed for the bot instance and the current update.

pub type Context(session, error, dependencies) {
  Context(
    key: String,
    update: update.Update,
    config: @internal Config,
    session: session,
    dependencies: dependencies,
    chat_subject: process.Subject(
      ChatInstanceMessage(session, error, dependencies),
    ),
    start_time: option.Option(timestamp.Timestamp),
    log_prefix: option.Option(String),
    bot_info: types.User,
  )
}

Constructors

  • Context(
      key: String,
      update: update.Update,
      config: @internal Config,
      session: session,
      dependencies: dependencies,
      chat_subject: process.Subject(
        ChatInstanceMessage(session, error, dependencies),
      ),
      start_time: option.Option(timestamp.Timestamp),
      log_prefix: option.Option(String),
      bot_info: types.User,
    )

    Arguments

    dependencies

    Non-persisted services/dependencies injected at bot init (DI container). Unlike session, dependencies is never persisted — it holds things like a db pool, http client, or i18n catalog. See telega.with_dependencies.

    start_time

    Used to calculate the duration of the conversation in logs

pub type Handler(session, error, dependencies) {
  HandleAll(
    handler: fn(
      Context(session, error, dependencies),
      update.Update,
    ) -> Result(Context(session, error, dependencies), error),
  )
  HandleCommand(
    command: String,
    handler: fn(
      Context(session, error, dependencies),
      update.Command,
    ) -> Result(Context(session, error, dependencies), error),
  )
  HandleCommands(
    commands: List(String),
    handler: fn(
      Context(session, error, dependencies),
      update.Command,
    ) -> Result(Context(session, error, dependencies), error),
  )
  HandleText(
    handler: fn(Context(session, error, dependencies), String) -> Result(
      Context(session, error, dependencies),
      error,
    ),
  )
  HandleHears(
    hears: Hears,
    handler: fn(Context(session, error, dependencies), String) -> Result(
      Context(session, error, dependencies),
      error,
    ),
  )
  HandleMessage(
    handler: fn(
      Context(session, error, dependencies),
      types.Message,
    ) -> Result(Context(session, error, dependencies), error),
  )
  HandleVoice(
    handler: fn(
      Context(session, error, dependencies),
      types.Voice,
    ) -> Result(Context(session, error, dependencies), error),
  )
  HandleAudio(
    handler: fn(
      Context(session, error, dependencies),
      types.Audio,
    ) -> Result(Context(session, error, dependencies), error),
  )
  HandleVideo(
    handler: fn(
      Context(session, error, dependencies),
      types.Video,
    ) -> Result(Context(session, error, dependencies), error),
  )
  HandlePhotos(
    handler: fn(
      Context(session, error, dependencies),
      List(types.PhotoSize),
    ) -> Result(Context(session, error, dependencies), error),
  )
  HandleWebAppData(
    handler: fn(
      Context(session, error, dependencies),
      types.WebAppData,
    ) -> Result(Context(session, error, dependencies), error),
  )
  HandleCallbackQuery(
    filter: CallbackQueryFilter,
    handler: fn(
      Context(session, error, dependencies),
      String,
      String,
    ) -> Result(Context(session, error, dependencies), error),
  )
  HandleChatMember(
    handler: fn(
      Context(session, error, dependencies),
      types.ChatMemberUpdated,
    ) -> Result(Context(session, error, dependencies), error),
  )
}

Constructors

  • HandleAll(
      handler: fn(
        Context(session, error, dependencies),
        update.Update,
      ) -> Result(Context(session, error, dependencies), error),
    )

    Handle all messages.

  • HandleCommand(
      command: String,
      handler: fn(
        Context(session, error, dependencies),
        update.Command,
      ) -> Result(Context(session, error, dependencies), error),
    )

    Handle a specific command.

  • HandleCommands(
      commands: List(String),
      handler: fn(
        Context(session, error, dependencies),
        update.Command,
      ) -> Result(Context(session, error, dependencies), error),
    )

    Handle multiple commands.

  • HandleText(
      handler: fn(Context(session, error, dependencies), String) -> Result(
        Context(session, error, dependencies),
        error,
      ),
    )

    Handle text messages.

  • HandleHears(
      hears: Hears,
      handler: fn(Context(session, error, dependencies), String) -> Result(
        Context(session, error, dependencies),
        error,
      ),
    )

    Handle text message with a specific substring.

  • HandleMessage(
      handler: fn(
        Context(session, error, dependencies),
        types.Message,
      ) -> Result(Context(session, error, dependencies), error),
    )

    Handle any message.

  • HandleVoice(
      handler: fn(Context(session, error, dependencies), types.Voice) -> Result(
        Context(session, error, dependencies),
        error,
      ),
    )

    Handle voice messages.

  • HandleAudio(
      handler: fn(Context(session, error, dependencies), types.Audio) -> Result(
        Context(session, error, dependencies),
        error,
      ),
    )

    Handle audio messages.

  • HandleVideo(
      handler: fn(Context(session, error, dependencies), types.Video) -> Result(
        Context(session, error, dependencies),
        error,
      ),
    )

    Handle video messages.

  • HandlePhotos(
      handler: fn(
        Context(session, error, dependencies),
        List(types.PhotoSize),
      ) -> Result(Context(session, error, dependencies), error),
    )

    Handle photo messages.

  • HandleWebAppData(
      handler: fn(
        Context(session, error, dependencies),
        types.WebAppData,
      ) -> Result(Context(session, error, dependencies), error),
    )

    Handle web app data messages.

  • HandleCallbackQuery(
      filter: CallbackQueryFilter,
      handler: fn(
        Context(session, error, dependencies),
        String,
        String,
      ) -> Result(Context(session, error, dependencies), error),
    )

    Handle callback query. Context, data from callback query and callback_query_id are passed to the handler.

  • HandleChatMember(
      handler: fn(
        Context(session, error, dependencies),
        types.ChatMemberUpdated,
      ) -> Result(Context(session, error, dependencies), error),
    )

    Handle chat member update (when user joins/leaves a group). The bot must be an administrator in the chat and must explicitly specify “chat_member” in the list of allowed_updates to receive these updates.

pub type Hears {
  HearText(text: String)
  HearTexts(texts: List(String))
  HearRegex(regex: regexp.Regexp)
  HearRegexes(regexes: List(regexp.Regexp))
}

Constructors

Limited context handed to pre-router middleware. A PreHandler runs once per incoming update inside the Bot actor — before any chat instance is spawned or session is loaded — so it only carries update-level data, not a session. Use it for cross-cutting concerns that apply to every update: anti-spam, analytics, and update deduplication (telega/idempotency).

pub type PreContext(dependencies) {
  PreContext(
    update: update.Update,
    config: @internal Config,
    dependencies: dependencies,
    bot_info: types.User,
  )
}

Constructors

  • PreContext(
      update: update.Update,
      config: @internal Config,
      dependencies: dependencies,
      bot_info: types.User,
    )

    Arguments

    dependencies

    The same injected services available to handlers via Context.

Pre-router middleware: a single global pass over every update, run before routing. Registered with telega.use_pre_handler and executed in the order added; the first one that returns Stop short-circuits the rest and the router. Because they run sequentially inside the single Bot actor, read-then-write logic (e.g. dedup) is race-free across concurrent updates.

pub type PreHandler(dependencies) =
  fn(PreContext(dependencies)) -> PreRouterResult

Decision returned by a PreHandler: keep processing the update through the router, or stop it here (drop it before routing).

pub type PreRouterResult {
  Continue
  Stop
}

Constructors

  • Continue

    Continue to the next pre-router middleware and, eventually, the router.

  • Stop

    Stop processing this update. The webhook/poller is told the update was acknowledged (so Telegram does not retry it) but no handler runs.

pub type SessionSettings(session, error) {
  SessionSettings(
    persist_session: fn(String, session) -> Result(session, error),
    get_session: fn(String) -> Result(
      option.Option(session),
      error,
    ),
    default_session: fn() -> session,
  )
}

Constructors

  • SessionSettings(
      persist_session: fn(String, session) -> Result(session, error),
      get_session: fn(String) -> Result(option.Option(session), error),
      default_session: fn() -> session,
    )

Values

pub fn cancel_conversation(
  bot bot: Bot(session, error, dependencies),
  key key: String,
) -> Nil

Stops waiting for any handler for specific key (chat_id)

pub fn drain(
  bot_subject bot_subject: process.Subject(BotMessage),
  timeout timeout: Int,
) -> Int

Begin a graceful drain of the bot.

Stops accepting new updates and blocks until all in-flight updates finish or timeout milliseconds elapse. Returns the number of updates that were in-flight when the drain started, or -1 if the timeout was reached before draining completed.

pub fn get_session(
  session_settings: SessionSettings(session, error),
  update: update.Update,
) -> Result(option.Option(session), error)
pub fn is_draining(
  bot_subject bot_subject: process.Subject(BotMessage),
) -> Bool

Whether the bot is currently draining (no longer accepting new updates).

Webhook adapters use this to answer 503 so Telegram retries the update after the deploy instead of dropping it.

pub fn next_session(
  ctx ctx: Context(session, error, dependencies),
  session session: session,
) -> Result(Context(session, error, dependencies), error)
pub fn start(
  registry registry: @internal Registry(
    ChatInstanceMessage(session, error, dependencies),
  ),
  config config: @internal Config,
  bot_info bot_info: types.User,
  router_handler router_handler: fn(
    Context(session, error, dependencies),
    update.Update,
  ) -> Result(Context(session, error, dependencies), error),
  pre_handlers pre_handlers: List(
    fn(PreContext(dependencies)) -> PreRouterResult,
  ),
  session_settings session_settings: SessionSettings(
    session,
    error,
  ),
  catch_handler catch_handler: fn(
    Context(session, error, dependencies),
    error,
  ) -> Result(Nil, error),
  dependencies dependencies: dependencies,
  chat_factory chat_factory: factory_supervisor.Supervisor(
    ChatInstanceArgs(session, error, dependencies),
    process.Subject(
      ChatInstanceMessage(session, error, dependencies),
    ),
  ),
  name name: option.Option(process.Name(BotMessage)),
) -> Result(
  actor.Started(process.Subject(BotMessage)),
  actor.StartError,
)
pub fn start_chat_instance(
  args: ChatInstanceArgs(session, error, dependencies),
) -> Result(
  actor.Started(
    process.Subject(
      ChatInstanceMessage(session, error, dependencies),
    ),
  ),
  actor.StartError,
)

Start a chat instance. Used as the template function for factory_supervisor. Self-registers in the registry on start (handles both first start and restart after crash).

pub fn wait_handler(
  ctx ctx: Context(session, error, dependencies),
  handler handler: Handler(session, error, dependencies),
  handle_else handle_else: option.Option(
    Handler(session, error, dependencies),
  ),
  timeout timeout: option.Option(Int),
) -> Result(Context(session, error, dependencies), error)

Pass any handler to start waiting

or - calls if there are any other updates timeout - the conversation will be canceled after this timeout

Search Document