23 β Telegram Web App (Mini App) asoslari¶
β¬ οΈ Oldingi: 22 β Majburiy obuna Β· π README Β· Keyingi: 24 β Web App xavfsizligi: initData β‘οΈ
Bu bobda: Telegram'ning eng kuchli kengaytmasi β Mini App (eski nomi Web App) bilan tanishamiz. Mini App β bu Telegram ichida, alohida brauzer ochmasdan ko'rinadigan to'liq HTML/CSS/JS sahifa. Hamster Kombat, Notcoin kabi mashhur "clicker"lar, do'kon ilovalari, anketalar β hammasi Mini App'lar. Bu bobda biz to'rtta ochish usulini o'rganamiz: (1) inline tugma
InlineKeyboard().webApp(...), (2) reply klaviaturaKeyboard().webApp(...), (3) chat yonidagi menyu tugmasibot.api.setChatMenuButton(...), (4) inline rejim. Brauzer tomonida ishlaydigantelegram-web-app.jsSDK'sini βwindow.Telegram.WebAppobyektining.ready(),.expand(),.MainButton,.BackButton,.HapticFeedback,.sendData()va.initDataxossalarini β ko'rib chiqamiz. Eng muhimi: Mini App botingizga ma'lumotni qanday qaytaradi βWebApp.sendData()orqali yuborilgan ma'lumotnibot.on("message:web_app_data")handler'idactx.message.web_app_data.datadan o'qishni o'rganamiz.Halollik eslatmasi: Bobning bot tomonidagi kodi β
message:web_app_datahandler'i,InlineKeyboard.webApp/Keyboard.webApptugma tuzilmasi,setChatMenuButtonpayload'i βnode'da offline ishga tushirib tekshirilgan (_verify_23.mjs, 9/9 o'tdi): soxta bot qurib,bot.api.config.use(...)transformeri bilan chiqayotgan payload va kelayotganweb_app_datao'qilishini tasdiqladik. Brauzer tomonidagi kod (window.Telegram.WebApp,.ready(),.MainButton,.sendData()) β bu Telegram klientining JavaScript SDK'si, faqat real Telegram ilovasida ishlaydi; bunday joylar "illustrativ" deb belgilangan.initDatani tasdiqlash β keyingi 24-bobda chuqur ochiladi.
Mini App nima va u botdan nimasi bilan farq qiladi¶
Hozirgacha biz qurgan botlar xabar almashish orqali ishlardi: foydalanuvchi /start yuboradi, bot matn yoki tugma qaytaradi. Bu β chat interfeysi. Lekin ba'zi vazifalar uchun chat tor: tovarlar katalogini varaqlash, kalendardan sana tanlash, xaritada nuqta belgilash, murakkab forma to'ldirish. Bularning hammasi uchun bizga to'liq grafik interfeys kerak.
Aynan shu yerda Mini App yordamga keladi. Mini App β bu siz yozgan oddiy veb-sahifa (HTML + CSS + JavaScript), Telegram uni o'z ilovasi ichida, alohida oynada ochadi. Foydalanuvchi Telegram'dan chiqmaydi, brauzer ochilmaydi β sahifa Telegram interfeysiga "yopishgandek" ko'rinadi. Telegram bu sahifaga maxsus JavaScript SDK beradi (window.Telegram.WebApp), uning yordamida siz Telegram'ning rangiga moslashishingiz, "asosiy tugma" chiqarishingiz, telefonni tebratish (haptic) va ma'lumotni botga qaytarishingiz mumkin.
Mashhur misollar β barchasi Mini App:
- Hamster Kombat, Notcoin β "clicker" o'yinlari (bularni 26-bobda o'zimiz quramiz).
- Do'kon botlari β savatcha, to'lov, katalog.
- Bank/hamyon botlari β balans, o'tkazma.
Eslatma: "Web App" va "Mini App" β bir narsa. Telegram 2023-yilda brendni "Mini Apps"ga o'zgartirdi, lekin API'da hamon
web_app,WebApp,web_app_datanomlari ishlatiladi. Shuning uchun kodda har doimweb_appko'rasiz, gapda esa "Mini App" deymiz.
Bot vs Mini App β kim nima qiladi¶
| Oddiy bot (chat) | Mini App (veb-sahifa) | |
|---|---|---|
| Interfeys | Xabarlar, tugmalar | To'liq HTML/CSS/JS |
| Qayerda ishlaydi | Telegram serverlari + sizning bot kodingiz | Foydalanuvchi qurilmasidagi brauzer dvigateli (Telegram ichida) |
| Til | Node.js (grammY) β server | HTML/CSS/JS β frontend |
| Hosting | Bot serveri (polling/webhook) | HTTPS veb-server (sahifani beradi) |
| Ma'lumot almashish | ctx.reply, callback_query |
WebApp.sendData(), HTTP so'rovlar |
Eng muhim tushuncha: bot va Mini App β ikki ALOHIDA dastur. Bot β Node.js serverda ishlaydi (siz o'rgangan grammY kodi). Mini App β foydalanuvchi qurilmasida ochiladigan veb-sahifa, uni siz alohida HTTPS manzilda joylashtirasiz. Ular bir-biri bilan ikki yo'l orqali "gaplashadi": (A) Mini App sendData() orqali botga matn yuboradi, (B) Mini App backend'ga HTTP so'rov yuboradi (25-bobda).
Diqqat: Mini App URL'i doimo HTTPS bo'lishi shart.
http://manzil ishlamaydi β Telegram uni ochmaydi. Mahalliy ishlab chiqishdangrokyokilocaltunnelkabi vositalar bilan vaqtinchalik HTTPS tunnel quriladi (buni 25-bobda ko'ramiz).
Mini App'ni ochishning 4 usuli¶
Mini App'ni foydalanuvchiga ochib berishning to'rtta yo'li bor. Hammasi bir xil HTTPS sahifani ochadi, lekin tugma turi va ma'lumot qaytarish imkoniyati farq qiladi.
Usul 1 β Inline tugma (InlineKeyboard().webApp)¶
Eng ko'p ishlatiladigan usul. Xabar ostiga "yopishgan" inline tugma quyiladi, bosilganda Mini App ochiladi:
import { Bot, InlineKeyboard } from "grammy";
const bot = new Bot(process.env.BOT_TOKEN);
bot.command("ilova", (ctx) => {
const ik = new InlineKeyboard().webApp("π Ilovani ochish", "https://mywebapp.example");
return ctx.reply("Quyidagi tugmadan ilovani oching:", { reply_markup: ik });
});
Bu tugmaning ichki tuzilmasi (offline tasdiqlangan):
const ik = new InlineKeyboard().webApp("Ochish", "https://mywebapp.example");
console.log(ik.inline_keyboard[0][0].text); // "Ochish"
console.log(ik.inline_keyboard[0][0].web_app.url); // "https://mywebapp.example"
console.log(ik.inline_keyboard[0][0].callback_data); // undefined β bu CALLBACK emas!
Diqqat: Inline
.webApp()tugmasicallback_datasaqlamaydi. Demakbot.callbackQuery(...)uni hech qachon ushlamaydi (06-07 boblardagi inline tugmalar bilan adashtirmang). Bu tugma bosilganda bot'ga update kelmaydi β faqat sahifa ochiladi. Ma'lumot qaytarish uchun yoanswerWebAppQuery(quyida), yo HTTP backend kerak.
Usul 2 β Reply klaviatura (Keyboard().webApp)¶
Yozish maydoni o'rnida turadigan reply tugma orqali ochish. Bu usulning maxsus afzalligi bor: faqat shu yo'l bilan ochilgan Mini App sendData() orqali botga to'g'ridan-to'g'ri ma'lumot yubora oladi (pastda batafsil).
import { Keyboard } from "grammy";
bot.command("forma", (ctx) => {
const kb = new Keyboard()
.webApp("π± Formani to'ldirish", "https://mywebapp.example/form")
.resized();
return ctx.reply("Formani ochish uchun tugmani bosing:", { reply_markup: kb });
});
Tuzilma (offline tasdiqlangan):
const kb = new Keyboard().webApp("Ilovani ochish", "https://mywebapp.example").resized();
console.log(kb.keyboard[0][0].text); // "Ilovani ochish"
console.log(kb.keyboard[0][0].web_app.url); // "https://mywebapp.example"
Diqqat: Reply klaviatura
.webApp()tugmasi faqat shaxsiy chatda ishlaydi (guruhda emas). Bu Telegram cheklovi βsendDatamexanizmi shaxsiy suhbatga bog'langan.
Usul 3 β Chat menyu tugmasi (setChatMenuButton)¶
Yozish maydoni yonidagi doimiy tugma. Odatda u yerda "β‘" (buyruqlar menyusi) turadi, lekin uni Mini App ochadigan tugmaga almashtirib qo'yish mumkin:
await bot.api.setChatMenuButton({
menu_button: {
type: "web_app",
text: "Do'kon",
web_app: { url: "https://mywebapp.example/shop" },
},
});
Bu yuboradigan payload (offline tasdiqlangan):
// calls dagi setChatMenuButton chaqiruvi:
// {
// method: "setChatMenuButton",
// payload: { menu_button: { type:"web_app", text:"Do'kon",
// web_app:{ url:"https://mywebapp.example/shop" } } }
// }
chat_id bermasangiz β bu standart (default) menyu tugmasi, ya'ni hamma foydalanuvchilarga ko'rinadi. Aniq chat_id bersangiz β faqat o'sha foydalanuvchiga. Standart holatga qaytarish uchun menu_button: { type: "commands" } yoki { type: "default" } yuboring.
Eslatma: Joriy menyu tugmasini o'qish uchun
bot.api.getChatMenuButton({ chat_id })bor β uMenuButtonqaytaradi (type"commands" | "web_app" | "default"). Ko'pincha buni BotFather orqali bir marta sozlab qo'yish ham yetarli, lekin kod orqali har foydalanuvchiga moslab qo'yish kuchli.
Usul 4 β Inline rejim¶
Bot sozlamalarida (BotFather: /setmenubutton yoki bot profilidagi maxsus tugma) Mini App'ni ulash mumkin. Bu kamroq ishlatiladi; amalda yuqoridagi uchta usul yetarli. Inline rejim (inlineQuery) bilan ham Mini App ochilishi mumkin, lekin u alohida mavzu (07-bobdagi inline rejimga qarang).
Brauzer tomoni: telegram-web-app.js SDK (illustrativ)¶
Endi Mini App'ning ichiga kiramiz. Sahifangiz Telegram ichida ochilganda, sizga window.Telegram.WebApp degan global obyekt beriladi. Bu kod brauzerda (Telegram ilovasining ichki veb-ko'rinishida) ishlaydi β bot kodingizdan butunlay alohida.
SDK'ni ulash uchun sahifangizning <head>'iga bitta <script> qo'shasiz:
Illustrativ: Quyidagi barcha
window.Telegram.WebAppkodi β brauzerda, real Telegram ichida ishlaydi. Uninode'da test qilib bo'lmaydi (chunkiwindowyo'q). Shuning uchun bu qism halollik bilan "illustrativ" deb belgilangan. Bot tomonidagi qabul qilish kodi esa offline tasdiqlangan.
Hayot sikli: .ready() va .expand()¶
// (brauzerda) β illustrativ
const tg = window.Telegram.WebApp;
tg.ready(); // Telegram'ga "sahifa yuklandi" deb signal beradi (yuklanish indikatorini o'chiradi)
tg.expand(); // oynani to'liq balandlikka kengaytiradi (aks holda yarim ekran bo'lib qoladi)
Diqqat:
.ready()ni doimo chaqiring, sahifa interfeysidan keyin. Aks holda Telegram sahifa hali yuklanmoqda deb hisoblaydi va yuklanish belgisi qotib qoladi β foydalanuvchi "ilova osilgan" deb o'ylaydi. Bu eng tez-tez uchraydigan boshlovchi xatosi.
Mavzu (theme) va ranglar¶
Telegram foydalanuvchining mavzusini (och/qorong'i) sizga uzatadi, shunda sahifangiz Telegram bilan bir xil ko'rinadi:
// (brauzerda) β illustrativ
const tg = window.Telegram.WebApp;
document.body.style.backgroundColor = tg.themeParams.bg_color;
document.body.style.color = tg.themeParams.text_color;
// CSS o'zgaruvchilari ham bor: var(--tg-theme-bg-color), var(--tg-theme-text-color), ...
.MainButton va .BackButton¶
Telegram sahifa pastida joylashgan asosiy tugmani va yuqorida orqaga tugmasini boshqarish imkonini beradi. Bular Telegram'ning o'z UI elementlari β ular sahifangizning bir qismi emas, lekin siz ularni kod bilan boshqarasiz:
// (brauzerda) β illustrativ
const tg = window.Telegram.WebApp;
tg.MainButton.setText("Buyurtmani yuborish");
tg.MainButton.show();
tg.MainButton.onClick(() => {
// tugma bosilganda nima bo'lishini shu yerga yozasiz
tg.sendData(JSON.stringify({ mahsulot: "Olma", soni: 3 }));
});
tg.BackButton.show();
tg.BackButton.onClick(() => tg.close()); // orqaga bosilsa ilovani yopadi
.HapticFeedback (tebranish)¶
Tugma bosilganda telefonni "titratish" β mobil ilova tuyg'usini beradi:
// (brauzerda) β illustrativ
tg.HapticFeedback.impactOccurred("medium"); // "light" | "medium" | "heavy"
tg.HapticFeedback.notificationOccurred("success"); // "error" | "success" | "warning"
.initData va .initDataUnsafe β DIQQAT¶
Telegram Mini App'ga foydalanuvchi haqida ma'lumot beradi: kim ochdi, qaysi tilda, qachon. Ikki ko'rinishda:
// (brauzerda) β illustrativ
tg.initData; // XOM satr: "query_id=...&user=...&auth_date=...&hash=..."
tg.initDataUnsafe; // PARSLANGAN obyekt: { user: { id, first_name, ... }, auth_date, hash, ... }
Diqqat (juda muhim):
initDataUnsafenomida "unsafe" (xavfli) so'zi bejiz emas. Bu ma'lumotni brauzer beradi va uni soxtalashtirish mumkin β yomon niyatli foydalanuvchiinitDataUnsafe.user.idni o'zgartirib, boshqa odam nomidan ish qilishi mumkin. Shuning uchun uni faqat ko'rsatish uchun ishlating (masalan "Salom, Ali!"), lekin hech qachon unga ishonib muhim qaror qabul qilmang (to'lov, balans, ruxsat). To'g'ri yo'l: xominitDatani botingiz serveriga yuborib, u yerda HMAC bilan tasdiqlash. Bu β keyingi 24-bobning asosiy mavzusi.
.sendData() va .close()¶
// (brauzerda) β illustrativ
tg.sendData("matn yoki JSON-string"); // botga yuboradi VA ilovani avtomatik yopadi
tg.close(); // ilovani shunchaki yopadi
sendData haqida keyingi bo'limda batafsil β bu Mini App'dan botga ma'lumot qaytarishning asosiy yo'li.
Ma'lumotni botga qaytarish: sendData oqimi¶
Mana eng muhim qism. Mini App'da foydalanuvchi nimadir tanladi (rang, mahsulot, sana) β buni botga qanday qaytaramiz?
Ikki butunlay boshqacha yo'l bor, va ularni adashtirish β keng tarqalgan xato.
Yo'l A β sendData (faqat reply-klaviatura webApp tugmasi)¶
Agar Mini App reply klaviatura .webApp() tugmasidan ochilgan bo'lsa, brauzerda tg.sendData(x) chaqirilganda:
- Telegram Mini App'ni avtomatik yopadi.
- Botga
messageupdate'i keladi, ichidaweb_app_dataxizmat xabari bo'ladi. - Bot uni
bot.on("message:web_app_data")bilan ushlaydi va matnnictx.message.web_app_data.datadan o'qiydi.
Bot tomonidagi handler (offline tasdiqlangan):
bot.on("message:web_app_data", (ctx) => {
const raw = ctx.message.web_app_data.data; // Mini App yuborgan xom matn
return ctx.reply(`Qabul qilindi: ${raw}`);
});
Odatda Mini App JSON yuboradi, biz uni JSON.parse bilan ochamiz (offline tasdiqlangan):
bot.on("message:web_app_data", (ctx) => {
const raw = ctx.message.web_app_data.data;
let payload;
try {
payload = JSON.parse(raw);
} catch {
return ctx.reply("Ma'lumot formati noto'g'ri.");
}
return ctx.reply(`Buyurtma: ${payload.mahsulot} x${payload.soni}`);
});
web_app_data obyektida button_text maydoni ham bor β qaysi tugma orqali ochilganini bildiradi (offline tasdiqlangan):
bot.on("message:web_app_data", (ctx) => {
console.log(ctx.message.web_app_data.button_text); // masalan "Formani to'ldirish"
});
Diqqat:
web_app_data.dataβ bu ishonchsiz ma'lumot. grammY tiplarida ham yozilgan: "a bad client can send arbitrary data in this field" (yomon klient bu maydonga ixtiyoriy ma'lumot yuborishi mumkin). Demak undan kelgan narsani DB'ga to'g'ridan-to'g'ri yozmang, validatsiyadan o'tkazing. To'liq ishonch uchuninitDataorqali tasdiqlash kerak β 24-bob.
Yo'l B β Inline / menyu tugmasi: sendData ISHLAMAYDI¶
Agar Mini App inline .webApp() tugmasidan yoki menyu tugmasidan ochilgan bo'lsa, tg.sendData() ishlamaydi β bot hech qanday web_app_data olmaydi. Bu holatda ikki variant:
answerWebAppQueryβ Mini App'da inline natija tayyorlab, uni chatga "joylash". Bu murakkabroq oqim: Mini App backend'ga so'rov yuboradi, backendbot.api.answerWebAppQuery(web_app_query_id, result)chaqiradi, natija chatga inline xabar sifatida tushadi.- HTTP backend β eng moslashuvchan yo'l: Mini App
fetch()bilan o'z backend'ingizga so'rov yuboradi, backend ma'lumotni saqlaydi yoki bot orqali xabar yuboradi. Bu β 25-bobning asosiy mavzusi.
Inline tugma callback ham, web_app_data ham yubormasligini offline tasdiqladik:
bot.command("ilova", (ctx) =>
ctx.reply("?", { reply_markup: new InlineKeyboard().webApp("Ochish", "https://mywebapp.example") }));
bot.callbackQuery(/.*/, (ctx) => ctx.answerCallbackQuery({ text: "Bu hech qachon chaqirilmaydi" }));
// /ilova dan keyin hech qanday callback kelmaydi -> answerCallbackQuery hech qachon ishlamadi.
Eslatma: Qoidani esda saqlang:
sendData->web_app_datafaqat REPLY-klaviatura.webApp()tugmasidan keladi. Agarweb_app_datahandleringiz hech qachon ishlamasa β birinchi navbatda tugmani inline'dan reply klaviaturaga o'zgartiring.
Oddiy Mini App HTML skeleti (illustrativ)¶
Mana eng kichik to'liq Mini App. Bu fayl HTTPS serverda joylashadi (masalan https://mywebapp.example/index.html), bot esa unga .webApp() tugmasi orqali havola beradi. Backend qism β 25-bobda.
<!doctype html>
<html lang="uz">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mening Mini App</title>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body>
<h1>Rang tanlang</h1>
<button id="qizil">Qizil</button>
<button id="kok">Ko'k</button>
<script>
const tg = window.Telegram.WebApp;
tg.ready(); // Telegram'ga tayyor signali
tg.expand(); // to'liq balandlik
// Telegram mavzusiga moslashish
document.body.style.backgroundColor = tg.themeParams.bg_color || "#ffffff";
function tanla(rang) {
tg.HapticFeedback.impactOccurred("light");
// sendData faqat reply-klaviatura webApp tugmasidan ochilganda ishlaydi:
tg.sendData(JSON.stringify({ rang }));
// sendData o'zi ilovani yopadi.
}
document.getElementById("qizil").onclick = () => tanla("qizil");
document.getElementById("kok").onclick = () => tanla("kok");
</script>
</body>
</html>
Bot tomonida bu ma'lumotni qabul qiluvchi handler (offline tasdiqlangan):
import { Bot, Keyboard } from "grammy";
const bot = new Bot(process.env.BOT_TOKEN);
bot.command("rang", (ctx) => {
const kb = new Keyboard()
.webApp("π¨ Rang tanlash", "https://mywebapp.example/index.html")
.resized();
return ctx.reply("Rang tanlash uchun tugmani bosing:", { reply_markup: kb });
});
bot.on("message:web_app_data", (ctx) => {
const { rang } = JSON.parse(ctx.message.web_app_data.data);
return ctx.reply(`Siz "${rang}" rangni tanladingiz!`, {
reply_markup: { remove_keyboard: true },
});
});
bot.start(); // illustrativ: jonli polling token + internet talab qiladi
Anti-eskirish: Internetda eski Telegraf yoki node-telegram-bot-api uchun "web_app" misollarini ko'p uchratasiz β ular
ctx.webAppDatayoki boshqa nomlardan foydalanadi. grammY'da bunday emas: ma'lumot doimoctx.message.web_app_data.datadan o'qiladi (filter querymessage:web_app_data). Bu Telegram Bot API'ning standart maydoni, grammY uni o'zgartirmaydi.Eslatma: Python ekvivalenti β aiogram (Python kitobi) da bu
@dp.message(F.web_app_data)vamessage.web_app_data.datako'rinishida bo'ladi. Tushuncha bir xil; faqat sintaksis farq qiladi.
Cross-link: keyingi qadamlar¶
Bu bob β Mini App'lar olamiga kirish eshigi. To'rt bob bu mavzuni davom ettiradi:
- 24-bob β Web App xavfsizligi:
initData.initDataUnsafega nega ishonib bo'lmasligi va xominitDatani HMAC bilan qanday tasdiqlash. Real ilovada bu majburiy. - 25-bob β Mini App backend. HTTPS hosting,
fetchbilan backend'ga so'rov,answerWebAppQuery, ma'lumotni DB'da saqlash. - 26-bob β Clicker o'yini. Hamster-uslubidagi to'liq Mini App: ball sanash, sessiya, real vaqt.
- Klaviaturalar asoslari (
.webApp()tugmalari) β 06-bobda batafsil ko'rilgan; agarKeyboard/InlineKeyboardquruvchilarini eslamasangiz, qaytib ko'ring.
Tez-tez uchraydigan xatolar¶
| Xato | Sabab | Yechim |
|---|---|---|
| Tugma bosilsa "URL invalid" | URL http:// (HTTPS emas) |
Mini App URL'i doimo https:// bo'lsin |
| Mini App ochiladi, lekin yuklanish belgisi qotadi | tg.ready() chaqirilmagan |
Sahifa yuklangach WebApp.ready() chaqiring |
| Mini App yarim ekranda qoladi | tg.expand() chaqirilmagan |
WebApp.expand() qo'shing |
web_app_data handler hech qachon ishlamaydi |
Inline yoki menyu tugmasidan ochilgan | sendData faqat reply klaviatura .webApp() tugmasidan ishlaydi |
| Reply webApp tugma guruhda ishlamaydi | Telegram cheklovi | Reply webApp + sendData faqat shaxsiy chatda |
bot.callbackQuery webApp tugmani ushlamaydi |
webApp tugma callback yubormaydi | Reply + web_app_data yoki HTTP backend ishlating |
initDataUnsafe.user.idga ishonib to'lov ochildi |
u soxtalashtirilishi mumkin | Xom initDatani serverda HMAC bilan tasdiqlang (24-bob) |
JSON.parse(web_app_data.data) xato beradi |
Mini App matn yubordi, JSON emas | try/catch bilan o'rang, validatsiya qiling |
window.Telegram undefined |
SDK script ulanmagan | <script src="...telegram-web-app.js"> qo'shing |
Mashqlar¶
Quyidagi mashqlarning ko'pi offline tekshiriladi β naqsh _verify_23.mjs dagi bilan bir xil: makeBot() soxta bot quradi, bot.api.config.use(...) transformeri chiqayotgan payload'ni ushlaydi; tugma tuzilmasini esa to'g'ridan-to'g'ri ik.inline_keyboard / kb.keyboard orqali assert qilamiz. web_app_data update'ini esa mkWebAppData(data, buttonText) yordamchisi bilan mock qilamiz. Buyruq update'iga entities:[{type:"bot_command",...}] qo'shishni unutmang. Brauzer (window.Telegram.WebApp) qismi offline tekshirilmaydi β uni illustrativ deb qoldiring.
Oson¶
- Inline webApp tugma.
new InlineKeyboard().webApp("Ochish", "https://x.example")quring vaik.inline_keyboard[0][0].web_app.urlto'g'ri ekanini,callback_dataesaundefinedekaniniassertqiling. - Reply webApp tugma.
new Keyboard().webApp("Ochish", "https://x.example").resized()quring vakb.keyboard[0][0].web_app.urlhamdakb.resize_keyboard === trueekanini tekshiring. web_app_datao'qish.bot.on("message:web_app_data")handler yozing, uctx.message.web_app_data.datani o'qibctx.replybilan qaytarsin.mkWebAppData("salom", "Tugma")update yuborib, javob matnisalomni o'z ichiga olganini tekshiring.button_textmaydoni.mkWebAppData("x", "Yuborish")update yuboring va handler ichidactx.message.web_app_data.button_text === "Yuborish"ekaniniassertqiling.
O'rta¶
setChatMenuButtonpayload.bot.api.setChatMenuButton({ menu_button: { type:"web_app", text:"Do'kon", web_app:{ url:"https://x.example" } } })chaqiring va transformer ushlagan payload'damenu_button.type === "web_app"vamenu_button.web_app.urlto'g'ri ekanini tekshiring.- Handler inline webApp tugmani payload'da uzatadi.
/ilovabuyrug'iga inline.webApp(...)tugmali xabar yuboruvchi handler yozing; transformer ushlagansendMessagepayload'idareply_markup.inline_keyboard[0][0].web_app.urlto'g'ri ekaniniassertqiling. - JSON ma'lumotni parslash.
web_app_datahandlerJSON.parse(ctx.message.web_app_data.data)qilib{mahsulot, soni}o'qisin vaBuyurtma: Olma x3qaytarsin.mkWebAppData('{"mahsulot":"Olma","soni":3}', "Buyurtma")bilan tekshiring. - Noto'g'ri JSON himoyasi. 7-mashqdagi handler'ga
try/catchqo'shing: agarJSON.parsexato bersaFormat noto'g'riqaytarsin.mkWebAppData("bu json emas", "X")yuborib, javobFormat noto'g'riekaniniassertqiling. - Inline webApp callback YUBORMAYDI.
/ilovaga inline.webApp(...)tugmali xabar vabot.callbackQuery(/.*/, ...)handler yozing./ilovadan keyin (callback update yubormay)answerCallbackQueryhech qachon chaqirilmaganini (callsda yo'qligini) tekshiring.
Qiyin¶
- To'liq oqim: reply webApp -> sendData -> qabul.
/rangbuyrug'i reply.webApp(...)tugmasi chiqarsin;web_app_datahandler kelgan rangni o'qib,remove_keyboard: truebilan tasdiq yuborsin./rang, keyinmkWebAppData('{"rang":"qizil"}', "Rang")yuboring va: (a) birinchisendMessagereply_markup'idakeyboardborligini, (b) ikkinchisendMessagematnidaqizilborligini vareply_markup.remove_keyboard === trueekaniniassertqiling. - Menyu tugmasini standartga qaytarish. Avval
setChatMenuButtonbilanweb_apptugma o'rnating, keyin{ menu_button: { type: "commands" } }bilan qaytaring.callsda ikkisetChatMenuButtonborligini va ikkinchisiningtype === "commands"ekanini tekshiring. callback_datayo'qligini ikkala tugma turi uchun isbotlash. Bitta funksiyada bir xil URL bilanInlineKeyboard().webApp(...)vaKeyboard().webApp(...)quring. Inline'daweb_app.urlborligini vacallback_datayo'qligini, reply'dakeyboardmaydoni borligini hamdainline_keyboardyo'qliginiassertqiling.- Dinamik webApp menyusi.
tovarMenyusi(tovarlar)funksiya yozing: massivdan har bir tovar uchun alohida qatorda inline.webApp(nom, "https://shop.example/" + id)tugma qursin.[{id:1,nom:"Olma"},{id:2,nom:"Anor"}]bilan chaqirib, ikkala tugmaweb_app.url'ihttps://shop.example/1vahttps://shop.example/2ekanini tekshiring.
Yechimlar
Hammasini $env:TEMP\grammy-probe ichida .mjs fayl sifatida saqlab node fayl.mjs bilan ishga tushiring. Umumiy yordamchilar (makeBot, mkText, mkWebAppData) _verify_23.mjsdagi bilan bir xil β bu yerda qaytarmaymiz.
1-mashq yechimi¶
import { InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
const ik = new InlineKeyboard().webApp("Ochish", "https://x.example");
assert.equal(ik.inline_keyboard[0][0].text, "Ochish");
assert.equal(ik.inline_keyboard[0][0].web_app.url, "https://x.example");
assert.equal(ik.inline_keyboard[0][0].callback_data, undefined);
console.log("1-mashq: OK");
webApp inline tugma web_app.url saqlaydi, callback_data esa yo'q β u callback emas.
2-mashq yechimi¶
import { Keyboard } from "grammy";
import assert from "node:assert/strict";
const kb = new Keyboard().webApp("Ochish", "https://x.example").resized();
assert.equal(kb.keyboard[0][0].web_app.url, "https://x.example");
assert.equal(kb.resize_keyboard, true);
console.log("2-mashq: OK");
Reply klaviatura tugmasi ham web_app.url'ni saqlaydi, lekin keyboard massivida (inline_keyboard emas).
3-mashq yechimi¶
// makeBot, mkWebAppData β _verify_23.mjs dan
import assert from "node:assert/strict";
const { bot, calls } = makeBot();
bot.on("message:web_app_data", (ctx) =>
ctx.reply(`Qabul: ${ctx.message.web_app_data.data}`));
await bot.handleUpdate(mkWebAppData("salom", "Tugma", 1));
const c = calls.find((x) => x.method === "sendMessage");
assert.equal(c.payload.text, "Qabul: salom");
console.log("3-mashq: OK");
message:web_app_data filter query xizmat xabarini ushlaydi; ma'lumot ctx.message.web_app_data.data da.
4-mashq yechimi¶
import assert from "node:assert/strict";
const { bot } = makeBot();
let bt = null;
bot.on("message:web_app_data", (ctx) => { bt = ctx.message.web_app_data.button_text; });
await bot.handleUpdate(mkWebAppData("x", "Yuborish", 1));
assert.equal(bt, "Yuborish");
console.log("4-mashq: OK");
web_app_data obyektida data bilan birga button_text β qaysi tugma orqali ochilgani.
5-mashq yechimi¶
import assert from "node:assert/strict";
const { bot, calls } = makeBot();
await bot.api.setChatMenuButton({
menu_button: { type: "web_app", text: "Do'kon", web_app: { url: "https://x.example" } },
});
const c = calls.find((x) => x.method === "setChatMenuButton");
assert.equal(c.payload.menu_button.type, "web_app");
assert.equal(c.payload.menu_button.web_app.url, "https://x.example");
console.log("5-mashq: OK");
setChatMenuButton β bu bot.api metodi; payload to'g'ridan-to'g'ri menu_button obyektini oladi.
6-mashq yechimi¶
import { InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
// makeBot, mkText β _verify_23.mjs dan
const { bot, calls } = makeBot();
bot.command("ilova", (ctx) =>
ctx.reply("Oching:", { reply_markup: new InlineKeyboard().webApp("Ochish", "https://x.example") }));
await bot.handleUpdate(mkText("/ilova", 1));
const c = calls.find((x) => x.method === "sendMessage");
assert.equal(c.payload.reply_markup.inline_keyboard[0][0].web_app.url, "https://x.example");
console.log("6-mashq: OK");
Transformer chiqayotgan sendMessageni ushlaydi; reply_markup ichida quruvchi qurgan inline_keyboard turadi.
7-mashq yechimi¶
import assert from "node:assert/strict";
const { bot, calls } = makeBot();
bot.on("message:web_app_data", (ctx) => {
const p = JSON.parse(ctx.message.web_app_data.data);
return ctx.reply(`Buyurtma: ${p.mahsulot} x${p.soni}`);
});
await bot.handleUpdate(mkWebAppData('{"mahsulot":"Olma","soni":3}', "Buyurtma", 1));
const c = calls.find((x) => x.method === "sendMessage");
assert.equal(c.payload.text, "Buyurtma: Olma x3");
console.log("7-mashq: OK");
Mini App odatda JSON yuboradi β JSON.parse bilan ochib, maydonlardan foydalanamiz.
8-mashq yechimi¶
import assert from "node:assert/strict";
const { bot, calls } = makeBot();
bot.on("message:web_app_data", (ctx) => {
let p;
try { p = JSON.parse(ctx.message.web_app_data.data); }
catch { return ctx.reply("Format noto'g'ri"); }
return ctx.reply(`Buyurtma: ${p.mahsulot}`);
});
await bot.handleUpdate(mkWebAppData("bu json emas", "X", 1));
const c = calls.find((x) => x.method === "sendMessage");
assert.equal(c.payload.text, "Format noto'g'ri");
console.log("8-mashq: OK");
web_app_data.data ishonchsiz β try/catch bilan noto'g'ri formatdan himoyalanamiz.
9-mashq yechimi¶
import { InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
// makeBot, mkText β _verify_23.mjs dan
const { bot, calls } = makeBot();
bot.command("ilova", (ctx) =>
ctx.reply("?", { reply_markup: new InlineKeyboard().webApp("Ochish", "https://x.example") }));
bot.callbackQuery(/.*/, (ctx) => ctx.answerCallbackQuery({ text: "Hech qachon" }));
await bot.handleUpdate(mkText("/ilova", 1));
assert.ok(calls.every((c) => c.method !== "answerCallbackQuery"));
console.log("9-mashq: OK (webApp tugma callback yubormaydi)");
Inline webApp tugma callback yubormaydi β shuning uchun biz callback update yubormaymiz va handler hech qachon ishga tushmaydi.
10-mashq yechimi¶
import { Keyboard } from "grammy";
import assert from "node:assert/strict";
// makeBot, mkText, mkWebAppData β _verify_23.mjs dan
const { bot, calls } = makeBot();
bot.command("rang", (ctx) =>
ctx.reply("Rang tanlang:", {
reply_markup: new Keyboard().webApp("Rang", "https://x.example").resized(),
}));
bot.on("message:web_app_data", (ctx) => {
const { rang } = JSON.parse(ctx.message.web_app_data.data);
return ctx.reply(`Tanlandi: ${rang}`, { reply_markup: { remove_keyboard: true } });
});
await bot.handleUpdate(mkText("/rang", 1));
await bot.handleUpdate(mkWebAppData('{"rang":"qizil"}', "Rang", 2));
const msgs = calls.filter((c) => c.method === "sendMessage");
assert.ok(msgs[0].payload.reply_markup.keyboard, "1-xabar reply klaviatura bo'lishi kerak");
assert.ok(msgs[1].payload.text.includes("qizil"));
assert.equal(msgs[1].payload.reply_markup.remove_keyboard, true);
console.log("10-mashq: OK");
To'liq oqim: reply webApp tugma -> Mini App sendData -> web_app_data -> bot tasdiq yuborib klaviaturani olib tashlaydi.
11-mashq yechimi¶
import assert from "node:assert/strict";
const { bot, calls } = makeBot();
await bot.api.setChatMenuButton({
menu_button: { type: "web_app", text: "Do'kon", web_app: { url: "https://x.example" } },
});
await bot.api.setChatMenuButton({ menu_button: { type: "commands" } });
const mb = calls.filter((c) => c.method === "setChatMenuButton");
assert.equal(mb.length, 2);
assert.equal(mb[1].payload.menu_button.type, "commands");
console.log("11-mashq: OK");
Menyu tugmasini standartga qaytarish β type: "commands" (yoki "default") yuborish.
12-mashq yechimi¶
import { Keyboard, InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
function ikkalasi(url) {
return {
inline: new InlineKeyboard().webApp("Ochish", url),
reply: new Keyboard().webApp("Ochish", url),
};
}
const { inline, reply } = ikkalasi("https://x.example");
assert.equal(inline.inline_keyboard[0][0].web_app.url, "https://x.example");
assert.equal(inline.inline_keyboard[0][0].callback_data, undefined);
assert.equal(inline.keyboard, undefined);
assert.equal(reply.keyboard[0][0].web_app.url, "https://x.example");
assert.equal(reply.inline_keyboard, undefined);
console.log("12-mashq: OK");
Ikkala tugma ham web_app.url saqlaydi, lekin inline inline_keyboard maydonida (callback_data'siz), reply esa keyboard maydonida.
13-mashq yechimi¶
import { InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
function tovarMenyusi(tovarlar) {
const ik = new InlineKeyboard();
tovarlar.forEach((t, i) => {
ik.webApp(t.nom, `https://shop.example/${t.id}`);
if (i < tovarlar.length - 1) ik.row(); // OXIRGISIDAN keyin .row() YO'Q
});
return ik;
}
const ik = tovarMenyusi([{ id: 1, nom: "Olma" }, { id: 2, nom: "Anor" }]);
assert.equal(ik.inline_keyboard.length, 2);
assert.equal(ik.inline_keyboard[0][0].web_app.url, "https://shop.example/1");
assert.equal(ik.inline_keyboard[1][0].web_app.url, "https://shop.example/2");
console.log("13-mashq: OK");
Har tovar uchun .webApp(...), va .row()ni faqat oxirgisidan oldin qo'yamiz. Gotcha: agar har tugmadan keyin .row() qo'ysangiz (oxirgisi ham), grammY oxirida bo'sh qator ([]) qoldiradi va inline_keyboard.length 1 taga oshib ketadi. Shuning uchun yo oxirgi .row()ni tashlab keting, yo bo'sh qatorni hisobga oling.
Yakunda¶
Bu bobda Mini App'lar olamiga kirdik:
- Mini App β Telegram ichida ochiladigan to'liq HTML/CSS/JS sahifa; bot bilan ikki alohida dastur. Sahifa HTTPS serverda joylashadi.
- Ochish usullari: inline
InlineKeyboard().webApp(...), replyKeyboard().webApp(...), menyusetChatMenuButton({ menu_button: { type:"web_app", ... } }), inline rejim. - Brauzer SDK (
telegram-web-app.js, illustrativ):WebApp.ready()/.expand()/.MainButton/.BackButton/.HapticFeedback/.sendData()/.close()va.initData/.initDataUnsafe. - Ma'lumot qaytarish:
WebApp.sendData(x)->bot.on("message:web_app_data")->ctx.message.web_app_data.data. Faqat reply-klaviatura.webApp()tugmasi bu mexanizmni ishga tushiradi; inline/menyu tugmalari uchunanswerWebAppQueryyoki HTTP backend kerak.
Bot tomonidagi tugma tuzilmalari, setChatMenuButton payload'i va web_app_data o'qilishi offline tasdiqlandi (9/9). Lekin biz hali bitta muhim narsani qoldirdik: initData orqali kelgan foydalanuvchi haqiqatan o'sha odam ekanini qanday isbotlash? initDataUnsafega ishonib bo'lmaydi. Buni β HMAC-SHA256 bilan tasdiqlashni β keyingi 24-bobda o'rganamiz. Bu real Mini App uchun majburiy qadam.
β¬ οΈ Oldingi: 22 β Majburiy obuna Β· π README Β· Keyingi: 24 β Web App xavfsizligi: initData β‘οΈ