How do I...?¶
Short answers to the things you'll actually want to do. Each one is a complete
handler body; drop it into the handle function from the
Quickstart. Every snippet here is compiled against the
package, so it works as written.
Reply to a command¶
if ctx.is_message() and Command("start").check(ctx.message()):
_ = ctx.answer("Welcome!")
Command("start") matches /start, /start@yourbot, and /start with args.
For several commands at once, use Commands(["help", "about"]).
Echo whatever the user types¶
if ctx.is_message():
_ = ctx.answer(ctx.message().text())
Send a photo or a file¶
_ = ctx.bot.send_photo(chat_id, "https://example.com/cat.jpg", caption="a cat")
# upload from disk
_ = ctx.bot.send_photo_file(chat_id, "/tmp/chart.png")
_ = ctx.bot.send_document_file(chat_id, "/tmp/report.pdf")
By URL or file_id it's instant; the *_file variants upload bytes. See
Sending for the full set.
Show buttons and react to a tap¶
if ctx.is_message() and Command("menu").check(ctx.message()):
var kb = InlineKeyboard()
kb.button("Yes", "ans:yes")
kb.button("No", "ans:no")
_ = ctx.answer("Pick one", "", kb.as_markup())
elif ctx.is_callback():
var cb = ctx.callback()
# stop the button spinner
ctx.ack()
_ = ctx.answer("You chose " + cb.data())
Ask a question and wait for the answer¶
This is what the per-chat state machine is for. Set a state, and on the next message check which state the chat is in:
if ctx.is_message() and Command("name").check(ctx.message()):
ctx.state.set_state("awaiting_name")
_ = ctx.answer("What's your name?")
elif ctx.is_message() and ctx.state.get_state() == "awaiting_name":
var name = ctx.message().text()
ctx.state.set_data("name", name)
# done, drop the state
ctx.state.clear()
_ = ctx.answer("Nice to meet you, " + name)
The state and its data are per chat, so two users talking to the bot don't step on each other. Chain more states for a multi-step form.
Restrict a command to certain users¶
Filters are data; an "is this an admin" check is just an if:
# your Telegram user id
comptime ADMIN_ID = 12345678
if ctx.is_message() and Command("shutdown").check(ctx.message()):
if ctx.message().from_user().id() == ADMIN_ID:
_ = ctx.answer("shutting down")
else:
_ = ctx.answer("not allowed")
Keep a menu in one message¶
Instead of sending a new message each tap, edit the one the button is on:
if ctx.is_callback():
var cb = ctx.callback()
ctx.ack()
var m = cb.message()
_ = ctx.bot.edit_message_text(
m.chat_id(), m.message_id(), "You picked " + cb.data()
)
Show "typing..." before a slow reply¶
_ = ctx.bot.send_chat_action(chat_id, "typing")
# ... do the slow work (a model call, building a file) ...
_ = ctx.answer(result)
Send the same message to many chats¶
Throttle so you don't trip Telegram's flood limit:
from mojogram import RateLimiter
var limiter = RateLimiter(rate=25.0, burst=25.0)
for uid in subscriber_ids:
# waits if we're going too fast
limiter.acquire()
_ = bot.send_message(uid, "Announcement")
Take a Telegram Stars payment¶
# 500 Stars
var prices = String('[{"label":"Pro plan","amount":500}]')
_ = ctx.bot.send_invoice(chat_id, "Pro", "One month", "sub_pro", "XTR", prices)
# then approve checkout when Telegram asks:
_ = ctx.bot.answer_pre_checkout_query(query_id, True)
See Polls, payments and admin for the rest of the flow.
Format text safely¶
User text can contain characters that break Markdown or HTML, so escape it:
from mojogram import escape_html
var name = ctx.message().from_user().first_name()
_ = ctx.answer("Hello <b>" + escape_html(name) + "</b>", "HTML")
Call a method that isn't wrapped¶
About 60 methods have typed wrappers. For the other 120-plus, build params and call by name:
from mojogram import Params
var p = Params()
p.put_int("chat_id", chat_id)
p.put_int("message_id", message_id)
_ = ctx.bot.call("unpinAllChatMessages", p)
Run it in production¶
For a few users, polling is enough (the Quickstart loop). For real traffic, switch to webhooks behind nginx; see Deployment.