### Bot Startup via Supervision Tree Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/configuration.md Configure bots to start within a supervision tree. This example shows how to start the ExGram supervisor and a custom bot with various options like polling method, token, and handler mode. ```elixir # lib/my_app/application.ex def start(_type, _args) do children = [ # Start ExGram supervisor (manages bot registry) ExGram, # Start bot {MyBot, [ method: :polling, # :polling or :webhook token: "bot_token", # Or use configured token setup_commands: true, # Auto-register commands get_me: true, # Fetch bot info on startup handler_mode: :async # :async or :sync }]} ] Supervisor.start_link(children, strategy: :one_for_one) end ``` -------------------------------- ### ExGram Test Utilities Setup Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/api-reference.md Include testing macros and setup in your test module by using `ExGram.Test`. This provides utilities for starting bots, expecting messages, and pushing updates. ```Elixir defmodule MyBotTest do use ExUnit.Case, async: true use ExGram.Test setup context do {:ok, bot_name} = ExGram.Test.start_bot(context, MyBot) {:ok, bot_name: bot_name} end test "sends welcome", %{bot_name: bot_name} do ExGram.Test.expect(:send_message, %{message_id: 1, text: "Welcome"}) ExGram.Test.push_update(bot_name, build_update("/start")) end end ``` -------------------------------- ### Setup OpentelemetryExGram during Application Startup Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/opentelemetry.md Call OpentelemetryExGram.setup/0 once when your application starts, before initializing bots. This ensures tracing is active from the beginning. ```elixir def start(_type, _args) do OpentelemetryExGram.setup() children = [ExGram, {MyBot, [method: :polling, token: token]}] Supervisor.start_link(children, strategy: :one_for_one) end ``` -------------------------------- ### ExGram Test Helper Setup Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/configuration.md Start the necessary test adapters and configure ExUnit for testing. This setup is typically placed in test/test_helper.exs. ```elixir {:ok, _} = ExGram.Adapter.Test.start_link() {:ok, _} = ExGram.Updates.Test.start_link() ExUnit.start( exclude: [:skip_in_test], capture_log: true ) ``` -------------------------------- ### Full Bot Test Example Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/testing.md This example demonstrates a complete bot test setup. It starts an isolated bot for each test, allowing for precise control over commands and callbacks. Use `push_update/2` to simulate incoming updates and `expect/2` to assert outgoing API calls. ```elixir defmodule MyApp.BotTest do use ExUnit.Case, async: true use ExGram.Test # sets up verify_on_exit! and set_from_context automatically alias ExGram.Model.{Update, Message, User, Chat, CallbackQuery} # Each test starts its own isolated bot instance with handler_mode: :sync (the default). # push_update/2 blocks until the handler has fully run, so no sleeps or polling needed. setup context do {bot_name, _} = ExGram.Test.start_bot(context, MyApp.Bot) {:ok, bot_name: bot_name} end describe "commands" do test "responds to /start", %{bot_name: bot_name} do ExGram.Test.expect(:send_message, fn body -> assert body[:chat_id] == 123 assert body[:text] =~ "Welcome" {:ok, %{message_id: 1, chat: %{id: 123}, text: body[:text]}} end) update = %Update{ update_id: 1, message: %Message{ message_id: 100, date: 1_700_000_000, chat: %Chat{id: 123, type: "private"}, from: %User{id: 123, is_bot: false, first_name: "Alice"}, text: "/start" } } # Returns only after the handler has completed - expectation is already consumed ExGram.Test.push_update(bot_name, update) end test "responds to /help with keyboard", %{bot_name: bot_name} do ExGram.Test.expect(:send_message, fn body -> assert body[:reply_markup] assert markup = body[:reply_markup] assert is_list(markup[:inline_keyboard]) {:ok, %{message_id: 2, chat: %{id: 123}, text: "Help menu"}} end) update = %Update{ update_id: 2, message: %Message{ message_id: 101, date: 1_700_000_000, chat: %Chat{id: 123, type: "private"}, from: %User{id: 123, is_bot: false, first_name: "Alice"}, text: "/help" } } ExGram.Test.push_update(bot_name, update) end end describe "callback queries" do test "handles button press", %{bot_name: bot_name} do ExGram.Test.expect(:answer_callback_query, true) ExGram.Test.expect(:send_message, fn body -> assert body[:text] == "Action completed" {:ok, %{message_id: 3, text: "Action completed"}} end) update = %Update{ update_id: 3, callback_query: %CallbackQuery{ id: "cbq-1", from: %User{id: 123, is_bot: false, first_name: "Alice"}, message: %Message{ message_id: 100, date: 1_700_000_000, chat: %Chat{id: 123, type: "private"} }, data: "action:approve" } } ExGram.Test.push_update(bot_name, update) end end end ``` -------------------------------- ### Bot Startup via Direct start_link Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/configuration.md Start a bot directly using `start_link/1`. This is a simpler approach for starting a single bot instance. ```elixir {:ok, pid} = MyBot.start_link( method: :polling, token: "bot_token" ) ``` -------------------------------- ### Start Bot with Handler Mode Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/api-reference.md Demonstrates starting a bot with different handler modes. `:async` is default in production, while `:sync` is default for tests. ```elixir {:ok, pid} = MyBot.start_link(handler_mode: :async) ``` ```elixir {bot_name, info} = ExGram.Test.start_bot(context, MyBot, handler_mode: :sync) ``` -------------------------------- ### ExGram Test Module Setup Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/middleware-and-testing.md Set up your test module using ExGram.Test for automatic testing configuration. Start an isolated bot instance for each test using ExGram.Test.start_bot. ```elixir defmodule MyBotTest do use ExUnit.Case, async: true use ExGram.Test setup context do {:ok, bot_name} = ExGram.Test.start_bot(context, MyBot) {:ok, bot_name: bot_name} end test "sends welcome message", %{bot_name: bot_name} do ExGram.Test.expect(:send_message, %{ message_id: 1, text: "Welcome!", chat: %{id: 123} }) update = %ExGram.Model.Update{ update_id: 1, message: %ExGram.Model.Message{ message_id: 100, chat: %ExGram.Model.Chat{id: 123, type: "private"}, from: %ExGram.Model.User{id: 456, is_bot: false, first_name: "Test"}, text: "/start" } } ExGram.Test.push_update(bot_name, update) ExGram.Test.verify!() end end ``` -------------------------------- ### Start Bot with Configuration in Application Supervisor Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/testing.md Pass bot configuration options on startup by defining a config entry for the bot's module and including it in the application's supervisor tree. This ensures the bot starts with the correct test-related options. ```elixir defmodule MyApp.Application do use Application def start(_type, _args) do bot_config = Application.get_env(:my_app, MyApp.Bot, []) children = [ # ... your other children {MyApp.Bot, bot_config} # ... ] opts = [strategy: :one_for_one, name: MyApp.Supervisor] Supervisor.start_link(children, opts) end end ``` -------------------------------- ### Implement Bot Handlers with ExGram DSL Source: https://github.com/rockneurotiko/ex_gram/blob/master/README.md Define command handlers and middleware for your bot. This example shows 'start', 'help' commands, and ignoring usernames. ```elixir defmodule MyApp.Bot do use ExGram.Bot, name: :my_bot, setup_commands: true import ExGram.Dsl.Keyboard command("start") command("help", description: "Show help") middleware(ExGram.Middleware.IgnoreUsername) def handle({:command, :start, _}, context) do answer(context, "Welcome! I'm your bot.") end def handle({:command, :help, _}, context) do message = """ Available commands: /start - Start the bot /help - Show this help """ keyboard = keyboard :inline do row do button "Test button", callback_data: "button" end end answer(context, message, reply_markup: keyboard) end def handle({:callback_query, %{data: "button"}}, context) do context |> answer_callback("Button clicked!") |> edit(:inline, "You clicked the button!") end end ``` -------------------------------- ### Start Test Adapter in test_helper.exs for Libraries Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/testing.md For libraries or when the bot is not started in the application tree for the test environment, start the test adapter directly in `test_helper.exs`. This makes the adapter available for tests. ```elixir {:ok, _} = ExGram.Adapter.Test.start_link() ExUnit.start() ``` -------------------------------- ### Complete Notification System Example Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/low-level-api.md A comprehensive example of a notification system using ExGram, demonstrating formatted messages with entities and inline keyboards. ```elixir defmodule MyApp.Notifications do # You can still use the ExGram.Dsl.* if you want, they are independent import ExGram.Dsl.Keyboard alias ExGram.Dsl.MessageEntityBuilder, as: B alias ExGram.Model.{InlineKeyboardMarkup, InlineKeyboardButton} def send_notification(user_id, type, data) do {message, entities} = format_message(type, data) keyboard = build_keyboard(type, data) ExGram.send_message(user_id, message, reply_markup: keyboard, entities: entities, bot: :my_bot) end defp format_message(:order_shipped, %{order_id: id, tracking: tracking}) do header = B.join(["📦", B.bold("Order Shipped!")]) order = B.join([ B.join([B.bold("Order"), B.code("##{id}"), "has been shipped"]), B.join([B.bold("Tracking:"), B.url(tracking)]) ], "\n") B.join([header, order], "\n\n") end defp build_keyboard(:order_shipped, %{tracking_url: url}) do keyboard :inline do row do button "Track Package", url: url end end end end ``` -------------------------------- ### Full ExGram Bot Example Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/commands.md A comprehensive example demonstrating the definition of multiple commands with varying scopes, translations, and handler implementations. ```elixir defmodule MyBot do use ExGram.Bot, name: :my_bot, setup_commands: true middleware(ExGram.Middleware.IgnoreUsername) # Visible to all users in all contexts command(:start, description: "Start the bot", lang: es: [description: "Iniciar el bot"], pt: [description: "Iniciar o bot"] ) # Only visible in private chats, with a translated name for Spanish command(:help, description: "Get help", scopes: [:all_private_chats], lang: [es: [command: "ayuda", description: "Obtener ayuda"]] ) # Only visible to group administrators command(:ban, description: "Ban a user", scopes: [:all_chat_administrators], lang: [es: [description: "Prohibir usuario"]] ) # Hidden command - no description, won't appear in the menu command(:debug) def handle({:command, :start, _msg}, context) do answer(context, "Welcome! Use /help for a list of commands.") end # Handles both /help and /ayuda (Spanish alias) def handle({:command, :help, _msg}, context) do answer(context, "Here is some help!") end def handle({:command, :ban, %{text: target}}, context) do answer(context, "Banned #{target}") end def handle({:command, :debug, _msg}, context) do answer(context, "Debug info: ...") end def handle(_, _context), do: :ok end ``` -------------------------------- ### Install Dependencies Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/installation.md Run `mix deps.get` after adding new dependencies to your `mix.exs` file. ```bash mix deps.get ``` -------------------------------- ### Test Setup with OpentelemetryExGram.Test Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/opentelemetry.md Utilize OpentelemetryExGram.Test.setup/1 within your tests for per-test setup of the telemetry handler and automatic cleanup. This simplifies testing of traced ExGram components. ```elixir setup context do {bot_name, _} = ExGram.Test.start_bot(context, MyBot) OpentelemetryExGram.Test.setup(bot_name) {:ok, bot_name: bot_name} end ``` -------------------------------- ### ExGram Bot Test Example Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/configuration.md Demonstrates how to test an ExGram bot using ExUnit and ExGram.Test. This includes starting the bot, expecting messages, and pushing updates. ```elixir defmodule MyBotTest do use ExUnit.Case, async: true use ExGram.Test setup context do # Automatically configured via use ExGram.Test {:ok, bot_name} = ExGram.Test.start_bot(context, MyBot) {:ok, bot_name: bot_name} end test "example", %{bot_name: bot_name} do ExGram.Test.expect(:send_message, %{message_id: 1}) ExGram.Test.push_update(bot_name, update) end end ``` -------------------------------- ### Start Test Adapter in Supervision Tree for Applications Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/testing.md If your bot is part of the application's supervision tree, start the ExGram test adapter before the bot. This ensures the adapter is active when the bot makes its initial API calls. ```elixir defmodule MyApp.Application do use Application def start(_type, _args) do bot_config = Application.get_env(:my_app, MyApp.Bot, []) app_children = [ # ... your other children {MyApp.Bot, bot_config} # ... ] # Notife the `test_children()` call children = test_children() ++ app_children opts = [strategy: :one_for_one, name: MyApp.Supervisor] Supervisor.start_link(children, opts) end defp test_children do if Mix.env() == :test do [ExGram.Adapter.Test] else [] end end end ``` -------------------------------- ### ExGram Get Bot Info Example Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/api-reference.md Example of retrieving information about the bot using the ExGram.get_me function. ```elixir # Get bot information {:ok, me} = ExGram.get_me() ``` -------------------------------- ### Basic Webhook Setup Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/polling-and-webhooks.md Configure ExGram for webhook mode within your application's supervision tree. Provide your bot token. ```elixir children = [ ExGram, {MyBot, [method: :webhook, token: "YOUR_TOKEN"]} ] ``` -------------------------------- ### Polling Startup Configuration Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/polling-and-webhooks.md Set up the ExGram application to start with polling enabled, including token and polling-specific options. ```elixir # lib/my_app/application.ex children = [ ExGram, {MyBot, [ method: :polling, token: "bot_token", # Polling options offset: nil, # Start from this update ID limit: 100, # Max updates per request (1-100) timeout: 30, # Long polling timeout allowed_updates: ["message", "callback_query"] ]}] ] Supervisor.start_link(children, strategy: :one_for_one) ``` -------------------------------- ### Example Test Using Helper Functions Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/middleware-and-testing.md An example test case demonstrating the usage of the `TestHelpers` module to build updates and interact with ExGram's testing utilities. ```elixir defmodule MyBotTest do use ExUnit.Case, async: true use ExGram.Test import TestHelpers test "handles start command" do ExGram.Test.expect(:send_message, %{message_id: 1}) ExGram.Test.push_update(bot_name, build_command_update("start")) ExGram.Test.verify!() end end ``` -------------------------------- ### Start Isolated Bot Instance Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/api-reference.md Starts an isolated bot instance for testing purposes. Useful for running tests in a controlled environment. ```elixir def start_bot(context, module, opts \ []) ``` -------------------------------- ### Verify Webhook Setup Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/polling-and-webhooks.md Retrieves and inspects the current webhook information for the bot. ```elixir {:ok, info} = ExGram.get_webhook_info() IO.inspect(info) # %ExGram.Model.WebhookInfo{ # url: "https://mybot.example.com/webhook", # has_custom_certificate: false, # pending_update_count: 0, # last_error_date: nil, # max_connections: 40 # } ``` -------------------------------- ### ExGram Middleware Examples Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/types.md Provides examples of how to define middleware using modules or anonymous functions. Used by the Bot module and dispatcher configuration. ```elixir {ExGram.Middleware.IgnoreUsername, []} {MyMiddleware, param: "value"} fn cnt, _opts -> cnt end ``` -------------------------------- ### Configure ExGram.Adapter.Test Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/configuration.md Set up the Test adapter for mocking API calls in tests. Ensure `test_environment` is true and start the adapter in your `test_helper.exs`. ```elixir config :ex_gram, token: "test_token", adapter: ExGram.Adapter.Test, test_environment: true ``` ```elixir # Start in test_helper.exs {:ok, _} = ExGram.Adapter.Test.start_link() ExUnit.start() ``` -------------------------------- ### ExGram.Test.use/1 Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/api-reference.md Include testing macros and setup in your test module. This macro simplifies the process of setting up and running tests for your ExGram bot. ```APIDOC ## ExGram.Test.use/1 ### Description Include testing macros and setup in your test module. ### Usage ```elixir defmodule MyBotTest do use ExUnit.Case, async: true use ExGram.Test setup context do {:ok, bot_name} = ExGram.Test.start_bot(context, MyBot) {:ok, bot_name: bot_name} end test "sends welcome", %{bot_name: bot_name} do ExGram.Test.expect(:send_message, %{message_id: 1, text: "Welcome"}) ExGram.Test.push_update(bot_name, build_update("/start")) end end ``` ``` -------------------------------- ### Automatic Command Setup with setup_commands: true Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/configuration.md Enable automatic registration of commands defined within the bot module. Use the `command/2` macro to define commands with descriptions. ```elixir defmodule MyBot do use ExGram.Bot, name: :my_bot, setup_commands: true command("start", description: "Start the bot") command("help", description: "Show help") command("admin", scope: "default", description: "Admin commands") end ``` -------------------------------- ### ExGram.Adapter.Req Configuration Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/adapters-and-api.md Configuration for the recommended Req adapter, including basic setup and advanced Req-specific options. ```APIDOC ## ExGram.Adapter.Req Configuration ### Description Configuration for the recommended Req adapter, including basic setup and advanced Req-specific options. ### Setup ```elixir # config/config.exs config :ex_gram, adapter: ExGram.Adapter.Req ``` ### Dependencies ```elixir # mix.exs defp deps do [ {:ex_gram, "~> 0.65"}, {:req, "~> 0.5.0"}, {:jason, "~> 1.4"} ] end ``` ### Advanced Configuration ```elixir # config/config.exs - Req-specific options config :req, http_errors: :raise, retry: :transient, max_retries: 3, receive_timeout: 15_000 ``` ``` -------------------------------- ### ExGram Bot Module Example Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/cheatsheet.md Define a bot module with commands, regex patterns, middlewares, and handlers. ```elixir defmodule MyBot.Bot do @bot :my_bot use ExGram.Bot, name: @bot, setup_commands: true # Declare commands command("start") command("help", description: "Show help") # Declare regex patterns regex(:email, ~r/\b[\w._%+-]+@[\w.-]+\.[A-Z|a-z]{2,}\b/) # Add middlewares middleware(ExGram.Middleware.IgnoreUsername) middleware(&my_middleware/2) # Init callback (optional) def init(opts) do # Setup before receiving updates :ok end # Handlers def handle({:command, "start", _}, context) do answer(context, "Hi!") end end ``` -------------------------------- ### start_bot/2-3 Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/api-reference.md Starts an isolated bot instance for testing purposes, allowing for process isolation during tests. ```APIDOC ## start_bot/2-3 ### Description Start an isolated bot instance for testing. ### Method `start_bot(context, module, opts \ [])` ### Parameters #### Path Parameters - `context` (map()) - Required - Test context (for process isolation) - `module` (module()) - Required - Bot module to start - `opts` (keyword()) - Optional - Bot options (handler_mode: :sync, etc.) ### Returns `{bot_name, bot_info}` ``` -------------------------------- ### Basic Polling Setup Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/polling-and-webhooks.md Configure ExGram for polling mode within your application's supervision tree. Ensure your bot token is provided. ```elixir children = [ ExGram, {MyBot, [method: :polling, token: "YOUR_TOKEN"]} ] ``` -------------------------------- ### Application Supervisor Configuration Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/multiple-bots.md Configure the application supervisor to start ExGram, the bot supervisor, and any necessary bot-related tasks. ```elixir @impl true def start(_type, _args) do children = [ ExGram, MyBot.BotSupervisor, {Task, &MyBot.BotSupervisor.start_bots/0}, # ... ] opts = [strategy: :one_for_one, name: MyBot.Supervisor] Supervisor.start_link(children, opts) end ``` -------------------------------- ### Context Struct Example Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/handling-updates.md Illustrates the structure of the ExGram context, including update details, bot name, bot info, and extra data. ```elixir %ExGram.Cnt{ update: %ExGram.Model.Update{}, name: :my_bot, bot_info: %ExGram.Model.User{} | nil, extra: %{} # More fields used internally } ``` -------------------------------- ### Log Bot Initialization Start Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/telemetry.md Logs when a bot dispatcher is initializing. Extracts bot identifier from metadata. ```elixir def handle_event([:ex_gram, :bot, :init, :start], _measurements, metadata, _) do Logger.info("[ExGram] bot initializing bot=#{metadata.bot}") end ``` -------------------------------- ### Multi-step Conversation: Initial Step Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/cheatsheet.md Start a multi-step conversation by setting user state and asking the first question. ```elixir def handle({:command, "order", _}, context) do # Store state somewhere (ETS, Agent, Database) set_user_state(extract_id(context), :awaiting_item) answer(context, "What would you like to order?") end ``` -------------------------------- ### Fly.io Environment Setup Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/polling-and-webhooks.md Sets environment secrets for a Fly.io deployment, including bot token, webhook secret, and webhook URL. ```bash fly secrets set BOT_TOKEN="123456:ABC..." WEBHOOK_SECRET="xyz..." fly secrets set WEBHOOK_URL="https://mybot.fly.dev/telegram/webhook" ``` -------------------------------- ### Manual Command Setup using ExGram.set_my_commands Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/configuration.md Manually set bot commands and their descriptions using the `set_my_commands/2` function. Specify the scope for the commands. ```elixir {:ok, _} = ExGram.set_my_commands([ %{command: "start", description: "Start the bot"}, %{command: "help", description: "Show help"} ], scope: %{type: "default"}) ``` -------------------------------- ### ExGram Test Environment Configuration Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/middleware-and-testing.md Configure ExGram for testing by setting the token, adapter, and test environment in config/test.exs. Ensure the Test adapter is started in test_helper.exs. ```elixir config :ex_gram, token: "test_token", adapter: ExGram.Adapter.Test, test_environment: true ``` ```elixir {:ok, _} = ExGram.Adapter.Test.start_link() ExUnit.start() ``` -------------------------------- ### Configure Tesla with Gun Adapter Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/installation.md Example of configuring Tesla to use the Gun HTTP client, including adding Gun to dependencies. ```elixir # In deps {:tesla, "~> 1.16"}, {:gun, "~> 2.0"} # In config config :tesla, adapter: Tesla.Adapter.Gun ``` -------------------------------- ### Configure Ex_Gram for Testing Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/flyio.md Sets up Ex_Gram for testing purposes, using a dummy token and adapter, and disabling command setup. This configuration is specific to the test environment. ```elixir import Config config :ex_gram, token: "NOTHING", adapter: ExGram.Adapter.Test config :my_bot, MyBot.Bot, token: "test_token", method: :test, username: "testbot", setup_commands: false ``` -------------------------------- ### Chaining Operations for /start Command Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/dsl-and-handlers.md Demonstrates chaining multiple operations, including sending messages and setting up reply markups, for the /start command. Includes a callback for handling results and logging. ```elixir def handle({:command, :start, message}, context) do context |> answer("Welcome to our bot! 🎉") |> answer("What would you like to do?", reply_markup: main_menu_keyboard()) |> on_result(fn {:ok, msg}, name -> Logger.info("Sent welcome message: #{msg.message_id}") end) end defp main_menu_keyboard do keyboard :inline do row do button "Option 1", callback_data: "opt1" button "Option 2", callback_data: "opt2" end end end ``` -------------------------------- ### Configure ExGram.Test Options Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/testing.md Customize the behavior of `use ExGram.Test` by disabling automatic setup for `set_from_context/1` or `verify_on_exit!/1`. This allows for manual control over isolation and verification. ```elixir # Only auto-verify, skip set_from_context (e.g. you call it manually) use ExGram.Test, set_from_context: false # Only set_from_context, skip auto-verify (e.g. you call verify! manually) use ExGram.Test, verify_on_exit: false ``` -------------------------------- ### Configure Bot Startup Options Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/cheatsheet.md Control built-in hooks like `get_me` and `setup_commands` via startup options. ```elixir # get_me: true (default) - fetches bot identity via getMe, available as context.bot_info # get_me: false - skips the API call (default in tests) {MyBot.Bot, [method: :polling, token: token, get_me: false]} ``` ```elixir # setup_commands: true - registers declared commands with Telegram at startup {MyBot.Bot, [method: :polling, token: token, setup_commands: true]} ``` -------------------------------- ### ExGram Router Tree Output Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/router.md Example output of the `mix ex_gram.router.tree` task, visualizing the bot's routing scope tree with indentation. ```text $ mix ex_gram.router.tree MyBot MyBot routing tree: ├── scope │ ├── filters: [Command(:start)] │ └── handle: &MyBot.Handlers.start/1 ├── scope │ ├── filters: [Command(:help)] │ └── handle: &MyBot.Handlers.help/1 └── scope ├── filters: [CallbackQuery([prefix: "proj:"]) [propagate]] ├── scope │ ├── filters: [CallbackQuery("change")] │ └── handle: &MyBot.Handlers.change_project/1 └── scope ├── filters: [CallbackQuery("delete")] └── handle: &MyBot.Handlers.delete_project/1 ``` -------------------------------- ### Initializing the Bot with init/1 Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/handling-updates.md Shows how to use the optional init/1 callback to set the bot's description and name before processing updates. ```elixir def init(opts) do # opts contains [:bot, :token] ExGram.set_my_description!( description: "This bot helps you manage tasks", bot: opts[:bot] ) ExGram.set_my_name!( name: "TaskBot", token: opts[:token] ) # Do some logic you need before starting your bots # MyBot.notify_admins_restart(opts[:bot]) :ok end ``` -------------------------------- ### Docker Compose Webhook Setup Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/polling-and-webhooks.md Docker Compose configuration for running an ExGram bot with a webhook, including Nginx for SSL termination and routing. ```yaml version: '3' services: bot: build: . ports: - "8080:8080" environment: BOT_TOKEN: ${BOT_TOKEN} WEBHOOK_URL: "https://mybot.example.com/telegram/webhook" WEBHOOK_SECRET: ${WEBHOOK_SECRET} networks: - bot-net nginx: image: nginx:latest ports: - "443:443" - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro networks: - bot-net networks: bot-net: ``` -------------------------------- ### ExGram Forward Message Example Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/api-reference.md Example of forwarding a message using ExGram.forward_message, including options to disable notifications. ```elixir # Forward a message {:ok, msg} = ExGram.forward_message( chat_id, from_chat_id, message_id, disable_notification: true ) ``` -------------------------------- ### Creating a Module-based Middleware Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/middlewares.md Implement the `ExGram.Middleware` behaviour for more complex logic. This example creates an authorization middleware that checks user IDs against an allowed list. ```elixir defmodule MyBot.AuthMiddleware do @behaviour ExGram.Middleware def call(context, opts) do user = ExGram.Dsl.extract_user(context) if authorized?(user.id, opts) do # User is authorized, continue context else # User is not authorized, halt processing ExGram.Dsl.answer(context, "⛔ Access denied") |> Map.put(:halted, true) end end defp authorized?(user_id, opts) do allowed_users = Keyword.get(opts, :allowed_users, []) user_id in allowed_users end end ``` -------------------------------- ### ExGram Update Routing Examples Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/README.md Provides examples of different message types that the ExGram dispatcher can route to specific handler functions. ```elixir def handle({:command, :start, message}, context) do # Matched /start command end def handle({:callback_query, query}, context) do # Matched inline button press end def handle({:text, "hello", message}, context) do # Matched exact text match end def handle({:regex, :pattern_name, message}, context) do # Matched regex pattern end ``` -------------------------------- ### Handling Specific Commands Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/handling-updates.md Pattern match on command tuples to handle specific commands like 'start' and 'help'. The `msg` parameter captures any text following the command. ```elixir def handle({:command, "start", msg}, context) do answer(context, "Welcome! You sent: #{msg}") end def handle({:command, "help", _msg}, context) do answer(context, """ Available commands: /start - Start the bot /help - Show this help /settings - Configure settings """) end ``` -------------------------------- ### Add ExGram and Bot to Supervision Tree Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/getting-started.md Modify `lib/my_bot/application.ex` to include `ExGram` and your bot module in the supervisor's children list. Ensure `ExGram` starts before your bot. ```elixir defmodule MyBot.Application do use Application @impl true def start(_type, _args) do children = [ ExGram, {MyBot.Bot, [method: :polling, token: Application.fetch_env!(:ex_gram, :token)]} ] opts = [strategy: :one_for_one, name: MyBot.Supervisor] Supervisor.start_link(children, opts) end end ``` -------------------------------- ### Basic ExGram Bot with FSM and Router Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/fsm.md This snippet demonstrates setting up a bot with ExGram.Router and ExGram.FSM. Ensure `use ExGram.Router` precedes `use ExGram.FSM`. It defines a registration flow with states for getting name and email. ```elixir defmodule MyBot do use ExGram.Bot, name: :my_bot, setup_commands: true use ExGram.Router use ExGram.FSM, storage: ExGram.FSM.Storage.ETS, flows: [MyBot.RegistrationFlow] command("register", description: "Start registration") scope do filter :command, :register handle &MyBot.Handlers.start_registration/1 end scope do filter :fsm_flow, :registration scope do filter :fsm_state, :get_name filter :text handle &MyBot.Handlers.got_name/2 end scope do filter :fsm_state, :get_email filter :text handle &MyBot.Handlers.got_email/2 end end scope do handle &MyBot.Handlers.fallback/1 end end ``` -------------------------------- ### Dynamic Bot Supervisor Implementation Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/multiple-bots.md Implement a DynamicSupervisor to manage multiple bots dynamically. This supervisor starts bots based on configurations, allowing for flexible addition and removal. ```elixir defmodule MyBot.BotSupervisor do use DynamicSupervisor @spec start_link(any()) :: Supervisor.on_start() | :ignore def start_link(_init_arg) do DynamicSupervisor.start_link(__MODULE__, [], name: __MODULE__) end @impl true def init(_init_arg) do DynamicSupervisor.init(strategy: :one_for_one) end def start_bots() do bots = Application.get_env(:my_bot, :bots) bots |> Enum.with_index() |> Enum.map(fn {{bot_name, bot}, index} -> %{ id: index, token: Keyword.fetch!(bot, :token), method: Keyword.fetch!(bot, :method), bot_name: bot_name, extra_info: Keyword.get(bot, :extra_info, %{}), bot: Keyword.fetch!(bot, :bot) } end) |> Enum.each(&start_bot/1) end def start_bot(bot) do name = String.to_atom("bot_#{bot.bot_name}_#{bot.id}") bot_options = [ token: bot.token, method: bot.method, name: name, id: name, bot_name: bot.bot_name, extra_info: bot.extra_info ] child_spec = {bot[:bot], bot_options} {:ok, _} = DynamicSupervisor.start_child(__MODULE__, child_spec) end end ``` -------------------------------- ### Build Actions with ExGram DSL Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/sending-messages.md Demonstrates how to queue multiple actions (answer, answer_photo) using the ExGram DSL builder pattern. Ensure the context is returned for actions to execute. ```elixir def handle({:command, "start", _msg}, context) do context |> answer("Welcome!") # Action 1: queued |> answer("Here's a menu:") # Action 2: queued |> answer_photo(photo_id) # Action 3: queued end # After handler returns, ExGram executes: action 1 → action 2 → action 3 ``` -------------------------------- ### Configure Multiple Bots with Different Methods Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/polling-and-webhooks.md Example of configuring multiple ExGram bots within the same application, specifying different update methods (polling or webhook) and tokens for each. ```elixir children = [ ExGram, {MyBot.DevBot, [method: :polling, token: dev_token]}, {MyBot.ProdBot, [method: :webhook, token: prod_token]}, {MyBot.OtherBot, [method: :webhook, token: other_token]} ] ``` -------------------------------- ### Testing Bot Initialization Calls Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/testing.md Configure bot initialization tests by setting `get_me: true` and `setup_commands: true` in `ExGram.Test.start_bot/3`. Ensure you set up corresponding expectations for `:get_me` and `:set_my_commands` before starting the bot. ```elixir # Example of setting up expectations and starting the bot # ExGram.Test.expect(:get_me, true) # ExGram.Test.expect(:set_my_commands, true) # ExGram.Test.start_bot(context, MyApp.Bot, get_me: true, setup_commands: true) ``` -------------------------------- ### Log Updates Worker Initialization Start Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/telemetry.md Logs when an updates worker (polling, webhook, etc.) starts initialization. Includes bot and method details. ```elixir def handle_event([:ex_gram, :updates, :init, :start], _measurements, metadata, _) do Logger.info("[ExGram] updates worker initializing bot=#{metadata.bot} method=#{metadata.method}") end ``` -------------------------------- ### ExGram Send Message Example Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/api-reference.md Example of sending a message using the ExGram.send_message function, specifying chat ID and message content with MarkdownV2 parsing. ```elixir # Send a message {:ok, message} = ExGram.send_message(chat_id, "Hello!", parse_mode: "MarkdownV2") ``` -------------------------------- ### Implement BotInit Behavior Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/cheatsheet.md Implement the `ExGram.BotInit` behavior for custom startup logic. ```elixir defmodule MyApp.SetupHook do @behaviour ExGram.BotInit @impl ExGram.BotInit def on_bot_init(opts) do token = opts[:token] # bot token bot = opts[:bot] # bot registered name extra = opts[:extra_info] # data from previous hooks case MyApp.Config.fetch(token) do {:ok, config} -> {:ok, %{app_config: config}} # merged into context.extra {:error, :not_found} -> :ok # non-fatal, no extra data {:error, reason} -> {:error, reason} # stops startup end end end ``` -------------------------------- ### get_chat_member_count Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/adapters-and-api.md Get the number of members in a chat. ```APIDOC ## get_chat_member_count ### Description Get chat member count. ### Method ExGram.get_chat_member_count(chat_id) ### Parameters - **chat_id** (any) - Required - The ID of the chat. ### Request Example ```elixir ExGram.get_chat_member_count(chat_id) ``` ### Response #### Success Response - **count** (integer) - The number of members in the chat. ### Response Example ```json {:ok, count} ``` ``` -------------------------------- ### Get Chat Member Count Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/adapters-and-api.md Retrieves the total number of members in a chat. ```elixir {:ok, count} = ExGram.get_chat_member_count(chat_id) ``` -------------------------------- ### Extract Chat from Context Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/sending-messages.md Helper function to get the chat object where the update occurred. ```elixir chat = extract_chat(context) ``` -------------------------------- ### Get Chat Member Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/adapters-and-api.md Retrieves information about a specific user's membership in a chat. ```elixir {:ok, member} = ExGram.get_chat_member(chat_id, user_id) ``` -------------------------------- ### Pagination: Initial List Display Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/cheatsheet.md Handle the initial command to display the first page of items with inline keyboard navigation. ```elixir import ExGram.Dsl.Keyboard def handle({:command, "list", _}, context) do show_page(context, 1) end defp show_page(context, page) do items = get_items(page) markup = keyboard :inline do row do button "⬅️", callback_data: "page:#{page - 1}" button "#{page}", callback_data: "current" button "➡️", callback_data: "page:#{page + 1}" end end edit(context, format_items(items), reply_markup: markup) end ``` -------------------------------- ### Get Chat Information Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/adapters-and-api.md Retrieves information about a specific chat (group, channel, or user). ```elixir {:ok, chat} = ExGram.get_chat(chat_id) ``` -------------------------------- ### Get Bot Information Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/low-level-api.md Retrieves information about the bot itself, such as its username, using the `get_me` method. ```elixir {:ok, %ExGram.Model.User{username: username}} = ExGram.get_me(bot: :my_bot) IO.puts("Bot username: @#{username}") ``` -------------------------------- ### Configure ExGram Without Framework Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/low-level-api.md Shows how to configure ExGram with a token and adapter in `config.exs` for use as a standalone library, without adding it to the supervision tree. ```elixir # config/config.exs config :ex_gram, token: "YOUR_BOT_TOKEN", adapter: ExGram.Adapter.Req # lib/my_app/application.ex # No need to add ExGram to supervision tree # lib/my_app.ex defmodule MyApp do def notify_users(message) do users = get_users() Enum.each(users, fn user -> ExGram.send_message(user.telegram_id, message) end) end end ``` -------------------------------- ### Verify Bot Token on Startup Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/errors.md This code snippet demonstrates how to verify the bot token by calling `ExGram.get_me()` during startup. It logs success or authentication errors. ```elixir # Verify token on startup def start_link(opts) do with {:ok, me} <- ExGram.get_me() do Logger.info("Bot started: #{me.first_name}") # ... continue startup else {:error, error} -> Logger.error("Failed to authenticate bot: #{error.message}") {:error, error} end end ``` -------------------------------- ### Configure ExGram and Bot for Test Environment Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/testing.md Set up ExGram and your bot to use the test adapter in the test environment. This configures ExGram to use the test adapter, specifies the test update method, and disables startup calls like get_me and setup_commands. ```elixir config :ex_gram, token: "test_token", adapter: ExGram.Adapter.Test config :my_app, MyBot.Bot, token: "test_token", method: :test, get_me: false, # Setting get_me: false we skip the get_me call on startup setup_commands: false # Setting setup_commands: false we skip setting up the commands on startup ``` -------------------------------- ### Get Webhook Info Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/adapters-and-api.md Retrieves the current status and configuration of the webhook. Returns an ExGram.Model.WebhookInfo struct. ```elixir {:ok, info} = ExGram.get_webhook_info() # Returns ExGram.Model.WebhookInfo ``` -------------------------------- ### ExGram.Middleware.Example Usage in Bot Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/api-reference.md Demonstrates how to integrate custom middleware into an ExGram bot using the `middleware/1` macro. ```APIDOC ## Example Usage in Bot ### Description Demonstrates how to integrate custom middleware into an ExGram bot. ### Method ```elixir defmodule MyBot do use ExGram.Bot, name: :my_bot middleware(ExGram.Middleware.IgnoreUsername) middleware(MyMiddleware) def handle(msg, context) do # Middlewares have already run end end ``` ``` -------------------------------- ### Enable SetupCommands Hook Source: https://github.com/rockneurotiko/ex_gram/blob/master/guides/bot-init-hooks.md Enable the SetupCommands hook to register bot commands with Telegram. This can be done either in the `use ExGram.Bot` macro or at startup. ```elixir use ExGram.Bot, name: :my_bot, setup_commands: true ``` ```elixir {MyApp.Bot, [method: :polling, token: token, setup_commands: true]} ``` -------------------------------- ### Get Bot Information Safely Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/adapters-and-api.md Retrieve information about the bot using the safe `ExGram.get_me/0` function. ```elixir {:ok, bot} = ExGram.get_me() ``` -------------------------------- ### Fetch Application Configuration Source: https://github.com/rockneurotiko/ex_gram/blob/master/_autodocs/api-reference.md Use `get/3` to fetch a value from the application configuration. It supports a default value and environment variable syntax for dynamic configuration. ```Elixir @spec get(atom(), atom(), term() | nil) :: term() def get(app, key, default \ nil) ``` ```Elixir # In config.exs config :ex_gram, token: {:system, "BOT_TOKEN"} # At runtime - automatically reads $BOT_TOKEN ExGram.Config.get(:ex_gram, :token) ```