Packages
ex_gram
0.61.0
0.67.0
0.66.0
0.65.0
0.64.0
0.63.0
0.62.0
0.61.0
0.60.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.1
0.55.0
0.54.0
0.53.0
0.52.2
0.52.1
0.52.0
0.51.1
0.51.0
0.50.2
0.50.1
0.50.0
0.41.0
0.40.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.1
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.5.0-rc6
0.5.0-rc5
0.5.0-rc4
0.5.0-rc3
0.5.0-rc2
Telegram Bot API low level and framework
Current section
Files
Jump to
Current section
Files
guides/cheatsheet.md
# ExGram Cheatsheet
Quick reference for common ExGram patterns and functions.
## Configuration
```elixir
# config/config.exs
config :ex_gram,
token: "BOT_TOKEN",
adapter: ExGram.Adapter.Req
```
## Bot module example
```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
```
## Supervision Tree
```elixir
children = [
ExGram, # Must come first
{MyBot.Bot, [method: :polling, token: token]}
]
```
## Handler Patterns
```elixir
# Commands
def handle({:command, "start", msg}, context)
# Text messages
def handle({:text, text, message}, context)
# Regex patterns (define with: regex(:name, ~r/pattern/))
def handle({:regex, :email, message}, context)
# Callback queries
def handle({:callback_query, callback}, context)
# Inline queries
def handle({:inline_query, query}, context)
# Location
def handle({:location, location}, context)
# Edited messages
def handle({:edited_message, edited_message}, context)
# Generic message
def handle({:message, message}, context)
# Default handler
def handle({:update, update}, context)
```
## DSL Functions - Sending
```elixir
# Text messages
answer(context, "Hello!")
answer(context, "Hello", parse_mode: "Markdown")
# Media
answer_photo(context, photo_id_or_file)
answer_document(context, doc_id_or_file)
answer_video(context, video_id_or_file)
answer_audio(context, audio_id_or_file)
answer_voice(context, voice_id_or_file)
answer_sticker(context, sticker_id)
answer_animation(context, animation_id)
# Callback queries
answer_callback(context, "Done!")
answer_callback(context, "Alert!", show_alert: true)
# Inline queries
answer_inline_query(context, results)
answer_inline_query(context, results, cache_time: 300)
```
## DSL Functions - Editing & Deleting
```elixir
# Edit message
edit(context, "New text")
edit(context, "New text", reply_markup: markup)
# Edit inline message
edit_inline(context, "New text")
# Edit only keyboard
edit_markup(context, new_markup)
# Delete message
delete(context)
delete(context, chat_id, message_id)
```
## DSL Functions - Chaining
```elixir
# Use result of previous action
context
|> answer("Sending photo...")
|> on_result(fn {:ok, message} ->
# Do something with message
:ok
end)
```
## File Formats
```elixir
# By Telegram file ID
"AgACAgIAAxkBAAI..."
# By local file path
{:file, "path/to/file.jpg"}
# By file content
{:file_content, binary_data, "filename.jpg"}
```
## Keyboards
```elixir
# Import the keyboard DSL
import ExGram.Dsl.Keyboard
# Simple inline keyboard
markup = keyboard :inline do
row do
button "Button 1", callback_data: "btn1"
button "Button 2", callback_data: "btn2"
end
row do
button "Button 3", callback_data: "btn3"
end
end
# Use in different methods that accept reply_markup
answer(context, "Choose:", reply_markup: markup)
# With URL button
keyboard :inline do
row do
button "Visit", url: "https://example.com"
end
end
# Reply keyboard (sticky keyboard)
keyboard :reply, [is_persistent: true] do
row do
reply_button "Help", style: "success"
end
end
# Keyboards inspect as a visual layout
# #InlineKeyboardMarkup<
# [ Button 1 (cb) ][ Button 2 (cb) ]
# [ Button 3 (url) ]
# >
# Verbose mode shows action values
inspect(markup, custom_options: [verbose: true])
# #InlineKeyboardMarkup<
# [ Button 1 (cb: "btn1") ][ Button 2 (cb: "btn2") ]
# [ Button 3 (url: "https://example.com") ]
# >
```
## Context Extractors
```elixir
# Chat/User info
extract_id(context) # Chat ID (User ID in private chats and Chat ID in groups)
extract_user(context) # User struct
extract_chat(context) # Chat struct
# Message info
extract_message_id(context) # Message id from any message type
extract_message_type(context) # :text, :photo, :document, etc.
# Query info
extract_callback_id(context) # Callback query ID
extract_inline_id_params(context) # Inline message params
# Update info
extract_update_type(context) # :message, :callback_query, etc.
extract_response_id(context) # Response ID for editing
```
## Update Types
```elixir
:message
:edited_message
:channel_post
:edited_channel_post
:inline_query
:chosen_inline_result
:callback_query
:shipping_query
:pre_checkout_query
:poll
:poll_answer
:my_chat_member
:chat_member
:chat_join_request
```
## Message Types
```elixir
:text
:photo
:video
:audio
:document
:voice
:sticker
:animation
:location
:contact
:poll
:dice
:game
:venue
```
## Low-Level API
```elixir
# Basic call
ExGram.send_message(chat_id, "Hello")
# With options
ExGram.send_message(chat_id, "Hello", parse_mode: "Markdown")
# With token
ExGram.send_message(chat_id, "Hello", token: "BOT_TOKEN")
# With named bot
ExGram.send_message(chat_id, "Hello", bot: :my_bot)
# Bang version (raises on error)
ExGram.send_message!(chat_id, "Hello")
# Common methods
ExGram.get_me()
ExGram.get_updates()
ExGram.send_photo(chat_id, photo)
ExGram.edit_message_text(chat_id, message_id, "New text")
ExGram.delete_message(chat_id, message_id)
ExGram.pin_chat_message(chat_id, message_id)
ExGram.get_chat(chat_id)
ExGram.get_chat_member(chat_id, user_id)
```
## Middleware
```elixir
# Function middleware
middleware(&my_middleware/2)
def my_middleware(context, opts) do
# Process context
context
end
# Module middleware
middleware(MyMiddleware)
middleware({MyMiddleware, opts})
# Built-in
middleware(ExGram.Middleware.IgnoreUsername)
# Add information to the extra field in `t:ExGram.Cnt.t/0`
context |> ExGram.Middleware.add_extra(:key, value)
context |> ExGram.Middleware.add_extra(%{key1: value1, key2: value2})
# Halt processing
context |> ExGram.Middleware.halt()
```
## Common Patterns
### Multi-step Conversation
```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
def handle({:text, text, _}, context) do
case get_user_state(extract_id(context)) do
:awaiting_item ->
set_user_state(extract_id(context), {:awaiting_quantity, text})
answer(context, "How many?")
{:awaiting_quantity, item} ->
clear_user_state(extract_id(context))
answer(context, "Ordered #{text}x #{item}!")
_ ->
answer(context, "I don't understand")
end
end
```
### Admin Check
```elixir
defmodule AdminMiddleware do
use ExGram.Middleware
def call(context, opts) do
user_id = ExGram.Dsl.extract_id(context)
admin_ids = Keyword.get(opts, :admins, [])
if user_id in admin_ids do
add_extra(context, :user_id, user_id)
else
context
|> ExGram.Dsl.answer("⛔ Admin only")
|> halt()
end
end
end
```
### Pagination
```elixir
import ExGram.Dsl.Keyboard
def handle({:command, "list", _}, context) do
show_page(context, 1)
end
def handle({:callback_query, %{data: "page:" <> page}}, context) do
page_num = String.to_integer(page)
context
|> answer_callback()
|> show_page(page_num)
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
```
## Debugging
```elixir
# Enable debug logging
config :logger, level: :debug
# Debug single request
ExGram.send_message(chat_id, "Test", debug: true)
# Log in handler
require Logger
def handle(update, context) do
Logger.debug("Received: #{inspect(update)}")
context
end
# Keyboards display as visual layouts in logs automatically
Logger.debug("Keyboard: #{inspect(markup)}")
# => Keyboard: #InlineKeyboardMarkup<
# [ OK (cb) ][ Cancel (cb) ]
# >
# Use verbose for action values
Logger.debug("Keyboard: #{inspect(markup, custom_options: [verbose: true])}")
# => Keyboard: #InlineKeyboardMarkup<
# [ OK (cb: "ok_pressed") ][ Cancel (cb: "cancel_action") ]
# >
```
## Testing
```elixir
# Start bot in noup mode
{MyBot.Bot, [method: :noup, token: "test"]}
# Build test context
%ExGram.Cnt{
update: update,
name: :my_bot,
halted: false,
responses: []
}
# Test handler
result = MyBot.Bot.handle({:command, "start", ""}, context)
assert result.responses != []
```
## Resources
- [ExGram Hex Docs](https://hexdocs.pm/ex_gram)
- [Telegram Bot API](https://core.telegram.org/bots/api)