Telegram Bot API framework with the single-message UI pattern.
Starting
Add to your supervision tree with a bot token and a module implementing Sexy.Bot.Session:
children = [
{Sexy.Bot, token: System.get_env("BOT_TOKEN"), session: MyApp.Session}
]Core workflow
The typical flow inside a session callback:
# 1. Build an Object from a plain map
object = Sexy.Bot.build(%{
chat_id: chat_id,
text: "Pick an option:",
kb: %{inline_keyboard: [[%{text: "Go", callback_data: "/go"}]]}
})
# 2. Send it — old message is deleted, new one saved automatically
Sexy.Bot.send(object)Single-message pattern
Each chat has one active message (screen). When you call send/1, Sexy:
- Detects content type (text, photo, video, animation, document)
- Calls the appropriate Telegram API method
- Deletes the previous message via
Session.get_message_id/1 - Saves the new message id via
Session.on_message_sent/4
This creates an app-like experience where the UI updates in place.
Sending files
To upload a local file or binary, set upload_type together with file and filename.
Supported types: :photo, :video, :animation, :document.
Sexy.Bot.build(%{
chat_id: chat_id,
upload_type: :document,
file: File.read!("report.csv"),
filename: "report.csv",
text: "Here is your report"
})
|> Sexy.Bot.send()
Sexy.Bot.build(%{
chat_id: chat_id,
upload_type: :photo,
file: File.read!("pic.jpg"),
filename: "pic.jpg",
text: "<b>Look</b>"
})
|> Sexy.Bot.send()The legacy media: "file" sentinel still works for documents and is equivalent
to upload_type: :document.
Notifications
Use notify/3 for messages that don't replace the current screen:
# Overlay with dismiss button
Sexy.Bot.notify(chat_id, %{text: "Saved!"})
# Replace current screen
Sexy.Bot.notify(chat_id, %{text: "Payment received!"}, replace: true)
# With navigation button
Sexy.Bot.notify(chat_id, %{text: "New order!"}, navigate: {"View", "/order id=42"})Telegram API
All standard Telegram Bot API methods are available as delegates:
send_message/2, send_photo/1, edit_text/1, delete_message/2,
answer_callback/3, send_invoice/6, and more. For any method not wrapped,
use request/2.
Summary
Functions
Answer a callback query with a pre-built map.
Answer a callback query with text and optional alert popup.
Confirm a pre-checkout query (for Telegram Payments).
Convert a map (or list of maps) into Sexy.Utils.Object struct(s).
Returns a specification to start this module under a supervisor.
Copy a message to another chat.
Delete all bot menu commands.
Delete a message. Pass after: seconds to delay deletion.
Edit message media. Body is a JSON-encoded string.
Edit message reply markup (buttons). Body is a JSON-encoded string.
Edit message text. Body is a map with :chat_id, :message_id, :text, etc.
Forward a message. Body is a JSON-encoded string.
Get chat info by chat_id.
Get chat member info.
Get bot info (getMe API method).
Poll for updates starting from the given offset. See Sexy.Bot.Api.get_updates/1.
Get the highest-resolution profile photo file_id for a user.
Send a notification message with optional dismiss/navigate buttons.
Refund a Telegram Stars payment.
Call any Telegram Bot API method by name.
Send an Object (or list of Objects) to Telegram.
Send an animation (GIF) by file_id. Body is a JSON-encoded string.
Upload an animation as multipart. file is a binary or path; kb is a JSON-encoded reply markup.
Show a chat action indicator. Type is one of: "txt" (typing),
"pic" (uploading photo), "vid" (uploading video).
Send a dice animation. Type is one of: "dice", "bowl", "foot",
"bask", "dart", "777".
Send a document as multipart upload.
Send a Telegram Stars invoice.
Send a pre-encoded JSON body as a message.
Send a text message with HTML parse mode.
Send a photo by file_id. Body is a JSON-encoded string.
Upload a photo as multipart. file is a binary or path; kb is a JSON-encoded reply markup.
Send a poll. Body is a JSON-encoded string.
Send a video by file_id. Body is a JSON-encoded string.
Upload a video as multipart. file is a binary or path; kb is a JSON-encoded reply markup.
Set bot menu commands from a comma-separated string.
Create a Wallet.tg payment order. Reads WALLET env var for the API key.
Types
@type tg_response() :: map()
Functions
Answer a callback query with a pre-built map.
Answer a callback query with text and optional alert popup.
Confirm a pre-checkout query (for Telegram Payments).
@spec build(map() | [map()]) :: Sexy.Utils.Object.t() | [Sexy.Utils.Object.t()]
Convert a map (or list of maps) into Sexy.Utils.Object struct(s).
Accepts any map with Object fields: :chat_id, :text, :media, :kb,
:entity, :update_data, :file, :filename, :upload_type.
Example
Sexy.Bot.build(%{chat_id: 123, text: "Hello!"})
#=> %Sexy.Utils.Object{chat_id: 123, text: "Hello!", ...}
Returns a specification to start this module under a supervisor.
See Supervisor.
Copy a message to another chat.
Delete all bot menu commands.
Delete a message. Pass after: seconds to delay deletion.
Examples
Sexy.Bot.delete_message(chat_id, mid)
Sexy.Bot.delete_message(chat_id, mid, after: 5)
Edit message media. Body is a JSON-encoded string.
Edit message reply markup (buttons). Body is a JSON-encoded string.
Edit message text. Body is a map with :chat_id, :message_id, :text, etc.
Forward a message. Body is a JSON-encoded string.
Get chat info by chat_id.
Get chat member info.
Get bot info (getMe API method).
Poll for updates starting from the given offset. See Sexy.Bot.Api.get_updates/1.
Get the highest-resolution profile photo file_id for a user.
@spec notify(integer(), map(), keyword()) :: tg_response()
Send a notification message with optional dismiss/navigate buttons.
See Sexy.Bot.Notification for full option details.
Examples
Sexy.Bot.notify(chat_id, %{text: "Done!"})
Sexy.Bot.notify(chat_id, %{text: "Saved!"}, after: 3)
Sexy.Bot.notify(chat_id, %{text: "Order!"}, navigate: {"View", "/order"}, after: 10)
Refund a Telegram Stars payment.
Call any Telegram Bot API method by name.
Example
body = Jason.encode!(%{chat_id: 123, text: "hi"})
Sexy.Bot.request(body, "sendMessage")
@spec send( Sexy.Utils.Object.t() | [Sexy.Utils.Object.t()], keyword() ) :: tg_response() | :ok
Send an Object (or list of Objects) to Telegram.
Handles the full single-message lifecycle: detect type, call API, delete old message, save new message id via Session.
Options
:update_mid—true(default) to manage the active message,falseto send without touching screen state
Examples
# Send a text screen
%{chat_id: id, text: "Hello"}
|> Sexy.Bot.build()
|> Sexy.Bot.send()
# Send without replacing the current screen
Sexy.Bot.send(object, update_mid: false)
Send an animation (GIF) by file_id. Body is a JSON-encoded string.
Upload an animation as multipart. file is a binary or path; kb is a JSON-encoded reply markup.
Show a chat action indicator. Type is one of: "txt" (typing),
"pic" (uploading photo), "vid" (uploading video).
Send a dice animation. Type is one of: "dice", "bowl", "foot",
"bask", "dart", "777".
Send a document as multipart upload.
Parameters
chat_id— Telegram chat idfile— binary file contentname— filename shown to the usertext— caption (HTML)kb— JSON-encoded reply markup
Send a Telegram Stars invoice.
Send a pre-encoded JSON body as a message.
Send a text message with HTML parse mode.
Send a photo by file_id. Body is a JSON-encoded string.
Upload a photo as multipart. file is a binary or path; kb is a JSON-encoded reply markup.
Send a poll. Body is a JSON-encoded string.
Send a video by file_id. Body is a JSON-encoded string.
Upload a video as multipart. file is a binary or path; kb is a JSON-encoded reply markup.
Set bot menu commands from a comma-separated string.
Example
Sexy.Bot.set_commands("start - Launch bot, help - Show help")
@spec start_link(keyword()) :: Supervisor.on_start()
Create a Wallet.tg payment order. Reads WALLET env var for the API key.