K ČEMU JSOU
Příklady aplikací
Objednávková obrazovka, vyhledávání, rezervace, jízdné. Každá karta níže je postavená z uzlů, které tato stránka rozdává, a všechny jsou živé — zmáčkněte je. Kde obrazovka potřebovala něco, co slovník neměl, chyběl slovníku uzel: place, time, item a total jsou ty čtyři, které vzešly ze stavby těchto karet.
Dva řádky jídelního lístku, velikost, co na to přijde, a kolik to dělá. Celá objednávka je jedna zpráva, která se pořád upravuje.
itemitemchoicechecklisttotalbutton
MargheritaSan Marzano, fior di latte, basil£11.50−1+
Diavola'Nduja, chilli, honey£13.00−1+TA MYŠLENKA
Co je widget
Klávesnice jsou řady tlačítek, takže každá otázka, která není které z těchto tlačítek, se změní v rozhovor: jedna zpráva na hodnotu a vůbec žádný způsob, jak na místě sebrat množinu, počet nebo den. Widget je odpověď na to: karta, kterou aplikace nakreslí z JSONu, jejž pošle bot, ve stejné zprávě a se stejným callbackovým kanálem pod tím.
Model stavu je ten, který klávesnice už má, jen zaostřený: stav patří botovi a klient nedrží nic, co mu nebylo dáno. Každé zmáčknutí je obyčejný řetězec události; bot aktualizuje vlastní kopii a pošle další specifikaci přes editMessageWidget, a karta se překreslí na místě. sendMessage přijímá parametr widget, takže specifikace jede spolu s odesláním a text zprávy je záložní varianta pro klienta, který ji neumí nakreslit.
POST /bot$KEY/sendMessage { "chat_id": -1001, "text": "Order #4821 — packed", // the fallback "widget": { "type": "card", "gap": "xs", "children": [ { "type": "row", "gap": "sm", "children": [ { "type": "icon", "icon": "cart", "tone": "accent" }, { "type": "text", "text": "Order #4821", "weight": "bold" }, { "type": "badge", "text": "PACKED", "tone": "muted" } ] }, { "type": "progress", "value": 0.66 }, { "type": "row", "gap": "sm", "children": [ { "type": "button", "label": "Advance", "primary": true, "tap": "card:step" }, { "type": "button", "label": "Reset", "tap": "card:reset" } ] } ] } } # the tap arrives as an ordinary callback_query with data "card:step", # and the bot pushes the next state into the same message: POST /bot$KEY/editMessageWidget { "chat_id": -1001, "message_id": 19, "widget": { …the next spec… } }
STAVEBNICE
Postavte si jeden
Vyberte ovládací prvek, otočte jeho knoflíky a zmáčkněte kartu. JSON je to, co váš bot posílá na sendMessage; řetězec pod ním je všechno, co se vrátí, když někdo zmáčkne to, co jste nakreslili.
stepper se ptá na kolik, v mezích.
Odpověď Telegramu jsou dvě tlačítka a počítadlo napsané do textu.
curl -X POST "$BASE/bot$KEY/sendMessage" \ -H 'content-type: application/json' \ -d '{ "chat_id": -1001, "text": "People — open the app to answer", "widget": { "type": "card", "gap": "sm", "children": [ { "type": "stepper", "label": "People", "min": 1, "max": 8, "value": 2, "tap": "seats:" } ] } }'
Zmáčkněte kartu v telefonu. Ať prvek dělá cokoli, k botovi dorazí jeden řetězec na cbq — víc se na ten kanál nevejde.
VÍC NAJEDNOU
Slož kartu
Jeden ovládací prvek odpovídá na jednu otázku a skoro nic, na co se bot ptá, není jedna otázka. Rezervace je počet, den, nastavení a tlačítko. card je uzel, který je drží, a jeho děti jsou seznam — takže ten seznam je rozvržení. Začněte od některé z těchto, nebo k ní přidejte řádky.
Upravujete stepper — ptá se na kolik, v mezích.
curl -X POST "$BASE/bot$KEY/sendMessage" \ -H 'content-type: application/json' \ -d '{ "chat_id": -1001, "text": "Open the app to answer", "widget": { "type": "card", "gap": "sm", "children": [ { "type": "stepper", "label": "People", "min": 1, "max": 8, "value": 2, "tap": "seats:" }, { "type": "date", "label": "Day", "placeholder": "Pick a day", "tap": "day:" }, { "type": "switch", "label": "Window seat", "value": false, "tap": "window" }, { "type": "button", "label": "Book the table", "tap": "book" } ] } }'
Zmáčkněte libovolný řádek karty v telefonu. Ať karta drží kolik chce uzlů, k botovi dorazí jeden řetězec — a proto každý adresovatelný uzel nese vlastní tap.
SLOVNÍK
Slovník je bílá listina
- rozvržení —
column,row,wrap,box,card,space,divider - zobrazení —
text,heading,note,banner,icon,badge,progress,markdown,code,spinner,glossary,total - vstup —
field,check,switch,choice,select,checklist,stepper,slider,rating,date,time,place,button,item,gallery,pagination,tree - složené —
checkbox_group,payment_request,swap_panel,ticket_offer: celé karty, které aplikace kreslí ze stavu dodaného botem - okno —
mini_app: jediný uzel, který kreslí něco, co tato aplikace nenapsala, a záměrně poslední v tabulce. Otvírá se nad konverzací, ne místo ní.
Jméno, které v seznamu není, nakreslí rámeček, který ho pojmenuje, místo aby nenakreslilo nic — formulář s tiše chybějícím řádkem je ten, který někdo odešle v domnění, že je úplný. Vzhled je token ("tone": "accent", "size": "lg"), rozhodnutý proti paletě aplikace: není tu vlastnost barvy, není tu vlastnost v pixelech a není tu ikona mimo bílou listinu — a právě to brání botovi nakreslit něco ve tvaru vlastního potvrzovacího listu této aplikace uvnitř konverzace, které už někdo věří. Syrový "color" se ignoruje a je na to test.
Jak se hodnota vrací kanálem, který nese jediný řetězec
- Ovládací prvek, jehož hodnotou je uzel sám —
button,check,switch— posílá svůj řetězectapbeze změny. Bot drží starý stav a ví, jaký musí být nový. - Ovládací prvek, který hodnotu nese —
select,choice,checklist,stepper,slider,rating,date,field— posílátaps hodnotou připojenou na konec: u"tap": "form:seats"dorazí volba čtyřky jakoform:seats:4. Volný text je poslední segment (todo:rename:2:Buy oat milk), takže ho odřízněte, místo abyste dělili na každé dvojtečce. - Uzel bez
tapse kreslí nehybný. Tak specifikace říká „tohle ukazuje, neptá se“ — a hotový formulář posílá další specifikaci úplně bez řetězcůtap, a právě tak přestane jít zmáčknout. callback_datamá v Bot API 64 bajtů, takže vybraná hodnota musí být krátká: seznam kódů aktiv projde, seznam vět ne.
Každý ovládací prvek a co se z něj vrací
Jedna tabulka, protože se do ní vejde celý protokol. Pošlete uzel vlevo; zmáčknutí vpravo je to, co dorazí jako callback_query.data. Každý z nich je převzatý z examples/widgetbot.rs, což je bot, kterého si můžete spustit.
| Uzel, který posíláte | Co se vrátí při zmáčknutí |
|---|---|
{"type": "button", "label": "Book it", "primary": true, "tap": "form:place"} | form:placeuzel je ta hodnota |
{"type": "switch", "id": "remind", "label": "Remind me", "on": false, "tap": "form:remind"} | form:remindholé — starý bit držíte vy a víte, jaký je nový |
{"type": "stepper", "id": "seats", "label": "Seats", "value": 2, "min": 1, "max": 8, "tap": "form:seats"} | form:seats:4hodnota je připojená na konec |
{"type": "select", "id": "sitting", "value": "main", "options": [{"value": "early", "label": "Early — 18:00"}, …], "tap": "form:sitting"} | form:sitting:earlyvalue té volby, nikdy její popisek |
{"type": "checklist", "id": "extras", "values": ["window"], "options": […], "tap": "form:extras"} | form:extras:cakedotčená položka, ne nová množina — překlápíte ji vy |
{"type": "slider", "id": "tip", "value": 10, "min": 0, "max": 30, "step": 5, "suffix": "%", "tap": "form:tip"} | form:tip:15 |
{"type": "rating", "id": "stars", "value": 4, "max": 5, "tap": "form:stars"} | form:stars:5 |
{"type": "date", "id": "when", "value": "", "tap": "form:when"} | form:when:2026-09-14 |
{"type": "field", "id": "email", "label": "E‑mail", "placeholder": "you@example.com", "tap": "signup:email"} | signup:email:ada@example.comvolný text je poslední segment |
{"type": "text", "text": "Order #4821"} — bez tap | nic. Nakreslené nehybně: tohle ukazuje, neptá se |
Takže obsluha dělí dvakrát a víckrát ne: jednou na sloveso, jednou na pole, a co zbude, je hodnota.
# data = "form:seats:4" let (verb, arg) = data.split_once(':').unwrap_or((data, "")); # form | seats:4 let (field, value) = arg.split_once(':').unwrap_or((arg, "")); # seats | 4 # Free text can hold colons, so it is the remainder — never a third split. # "todo:rename:2:Buy oat milk, 2 tins" is rename, item 2, and the rest.
Smyčka, od začátku do konce
Tři volání, a to prostřední patří aplikaci. To je celé — karta, která se na něco ptá, zmáčknutí, a karta překreslená s odpovědí uvnitř.
Táž tři volání jako v bloku níže, přehraná. Zmáčkněte plus a dívejte se, jak počítadlo zůstává, kde je: aplikace odeslala seats:3 a nemá se čeho jiného chytit. Změní se, až dorazí další specifikace — a to je celý model stavu v jednom ovládacím prvku a důvod, proč v tomto protokolu nikde nejsou delty.
# 1. the bot asks. `text` is the fallback for a client that cannot draw widgets. POST /bot$KEY/sendMessage { "chat_id": -1001, "text": "Table for two — 2 seats, no day yet", "widget": { "type": "card", "gap": "xs", "children": [ { "type": "row", "gap": "sm", "children": [ { "type": "icon", "icon": "cart", "tone": "accent" }, { "type": "text", "text": "Table for two", "weight": "bold" } ] }, { "type": "stepper", "id": "seats", "label": "Seats", "value": 2, "min": 1, "max": 8, "tap": "form:seats" }, { "type": "date", "id": "when", "label": "Day", "value": "", "tap": "form:when" }, { "type": "note", "text": "2 seats, no day yet" }, { "type": "button", "label": "Book it", "primary": true, "tap": "form:place" } ] } } # 2. somebody moves the stepper to four — this arrives from getUpdates { "update_id": 43, "callback_query": { "id": "cb-9", "data": "form:seats:4", "from": { "id": 5501, "first_name": "Ada" }, "message": { "message_id": 19, "chat": { "id": -1001 } } } } # 3. answer first — the control is waiting on it — then redraw with the new state POST /bot$KEY/answerCallbackQuery { "callback_query_id": "cb-9", "text": "4 at the table." } POST /bot$KEY/editMessageWidget { "chat_id": -1001, "message_id": 19, "widget": { …the same card, "value": 4… } }
Odmítnutí jsou táž tři volání bez toho třetího: odpovězte "One to eight." a nové specifikace neposílejte, a karta zůstane, jak byla. Selhat potichu nejde, protože ovládací prvek se kreslí čekající, dokud odpověď nedorazí.
Ptát se na něco napsaného
field je jediný ovládací prvek, který nese slova. Vrací se to, co bylo napsáno, jako poslední segment — a co z toho dělá formulář místo čtyř zpráv, je to, že i tvar patří vám: specifikace může vypěstovat řádek, který tam před chvílí nebyl.
# the bot asks
{ "type": "field", "id": "email-1", "label": "E-mail",
"placeholder": "you@example.com", "value": "", "tap": "signup:email" }
# what arrives
"data": "signup:email:ada@exmaple"
# the bot judges — nothing on the device knows what an address looks like —
# and sends the same field back wearing the verdict
{ "type": "field", "id": "email-2", "label": "E-mail",
"value": "ada@exmaple", "error": "That address has no dot after the @.",
"tap": "signup:email" }
# …with a banner above it, so the card says how many things need fixing
{ "type": "banner", "tone": "danger", "text": "One thing needs fixing." }Dvě věci o poli stojí za to vědět, než nějaké napíšete. Hlásí se, když se z něj odejde, ne při každé klávese — zmáčknutí tedy dorazí jednou, s tím, co bylo napsáno. A jeho value je jen to, s čím políčko začíná: abyste pole vyprázdnili nebo ho vrátili prázdné pro druhý pokus, dejte mu nové id. Proto identifikátory výše nesou číslo kola (email‑1, email‑2) — nová identita je způsob, jak se políčko vrátí prázdné.
Tři celé karty, za cenu stavového automatu
todo_list, swap_panel a ticket_offer jsou složené: kartu kreslí aplikace, stav dodává bot. Slovník zmáčknutí patří kartě.
"widget": {
"type": "todo_list", "title": "Shopping",
// the two the bot decides. A full list shows no Add at all,
// rather than an Add that answers no.
"can_add": true, "can_edit": true,
"items": [
{ "id": 0, "text": "Oat milk", "done": true },
{ "id": 1, "text": "Coffee", "done": false }
]
}
# There is no "tap" on a composite: the card's vocabulary is the card's,
# and the app sends these of its own accord.
# ticking the second line → "todo:toggle:1"
# renaming it → "todo:rename:1:Coffee, the good one"
# the Add button → "todo:add" (bare — you name the new item)
# retitling the card → "todo:title:This week"Kontrola je na vás a není to pravidlo o pořádku
Každý vstupní uzel přijímá hint a error, a obojí jsou slova bota. Nic v kreslítku neví, jak vypadá e-mailová adresa ani že zaškrtávací seznam má maximum: hodnota jde nahoru, bot soudí a další specifikace nese rozsudek. Klient, který by soudil sám, by byl druhým názorem na pravidla bydlící jinde, a ty dva by se rozešly ten den, kdy ta vaše změníte — potichu a směrem k tomu něco propustit. Kontrolujte znovu i na vstupu: schovat tlačítko nezastaví přehranou událost.
VIDĚT TO, ANIŽ BYSTE PSALI BOTA
Nastavení → Dynamické widgety v aplikaci projde celou smyčku s atrapou na druhém konci: tytéž specifikace, tytéž řetězce událostí a pod každou kartou panel, který doslova ukazuje obě poloviny drátu. Potom cargo run -p botapi --example widgetbot je těch samých šest karet poháněných opravdovým botem — a to je verze, která umí ukázat i zmáčknutí odmítnuté, přišlé dvakrát nebo propadlé.