Commands And Inline Keyboards

View Source

This example combines ordered routes, an inline keyboard, and callback query acknowledgement. The complete tested source is examples/inline_keyboard_bot.ex.

Define The Handler

An ordered route list keeps a small bot easy to scan. The first matching route runs; a fallback returns :ignore for updates this bot does not use.

defmodule MyApp.MenuBot do
  @behaviour Nadia.Handler

  alias Nadia.Context
  alias Nadia.Dispatcher
  alias Nadia.Model.{InlineKeyboardButton, InlineKeyboardMarkup}

  @impl Nadia.Handler
  def handle_update(_update, context) do
    Dispatcher.dispatch(context, [
      {:command, "start", {__MODULE__, :show_menu}},
      {:callback, {:prefix, "color:"}, {__MODULE__, :choose_color}},
      {:fallback, {__MODULE__, :ignore}}
    ])
  end

  def show_menu(context) do
    keyboard = %InlineKeyboardMarkup{
      inline_keyboard: [
        [
          %InlineKeyboardButton{text: "Blue", callback_data: "color:blue"},
          %InlineKeyboardButton{text: "Green", callback_data: "color:green"}
        ]
      ]
    }

    Context.reply(context, "Choose a color:", reply_markup: keyboard)
  end

  def choose_color(context, %{data: "color:" <> color}) do
    with :ok <- Context.answer_callback(context, text: "Saved"),
         {:ok, _message} <- Context.reply(context, "You chose #{color}.") do
      :ok
    end
  end

  def ignore(_context), do: :ignore
end

Always answer a callback query, even when no notification text is needed. Telegram clients keep showing a progress indicator until the bot calls answerCallbackQuery. A callback from an inline message may not have a chat, so Context.reply/3 can return an error; use the callback's inline_message_id with a direct Nadia editing method for that case.

Receive Both Update Types

Add the handler to your supervision tree and request the update types used by the routes:

children = [
  {Nadia.Polling,
   handler: MyApp.MenuBot,
   allowed_updates: ["message", "callback_query"],
   timeout: 30}
]

When commands may include a bot suffix such as /start@my_bot, pass the bot's username to polling:

{Nadia.Polling,
 handler: MyApp.MenuBot,
 bot_username: "my_bot",
 allowed_updates: ["message", "callback_query"]}

Without :bot_username, unsuffixed commands still match, while suffixed commands deliberately do not.