06 β Klaviaturalar: reply va inline¶
β¬ οΈ Oldingi: 05 β Xabar yuborish, formatlash va media Β· π README Β· Keyingi: 07 β Callback query va inline rejim β‘οΈ
Bu bobda: Telegram botning eng ko'zga ko'rinadigan qismi β tugmalarni o'rganamiz. Telegram'da ikki xil klaviatura bor va ularni aralashtirib yuborish boshlovchilarning eng tez-tez xatosi: reply klaviatura (chatdagi yozish maydonini almashtiradigan tugmalar β bosilganda oddiy matn yuboradi) va inline klaviatura (xabar ostiga "yopishgan" tugmalar β bosilganda matn emas,
callback_queryyuboradi). Biz grammY'ningKeyboardvaInlineKeyboardquruvchilarini (builder) ishlatishni β.text(),.row(),.resized(),.oneTime(),.persistent()va inline uchun.url(),.webApp(),.switchInline()β o'rganamiz; tugmalarni qatorlarga (.row()) va ustunlarga (.toFlowed()) qanday joylashtirishni, klaviaturani qanday olib tashlashni (remove_keyboard), massivdan dinamik klaviatura qurishni vacallback_datani qanday tejamli loyihalashni ko'ramiz.Halollik eslatmasi: Bu bobning kodi offline ishga tushirib tekshirilgan β
Keyboard/InlineKeyboardquruvchilari haqiqatan qanday tuzilma (keyboard/inline_keyboardmassivlari,resize_keyboardva boshqa bayroqlar) qurishinode'da tasdiqlangan, va handlerlar chiqaradiganreply_markuppayload'ibot.api.config.use(...)transformeri bilan ushlab tekshirilgan (_verify_06.mjs, 14/14 o'tdi). Tugmalarning vizual ko'rinishi va bosilganda Telegram klienti yuboradigan haqiqiy update'lar β bu jonli ishlash, real telefon/token talab qiladi; bunday joylar "illustrativ" deb belgilangan.
Ikki xil klaviatura β nega bu farq muhim¶
Telegram'da tugma qo'yishning ikki butunlay boshqacha yo'li bor. Avval farqni miyaga mahkam o'rnataylik, chunki keyingi hamma narsa shunga tayanadi.
Reply klaviatura β telefon klaviaturasi turadigan joyda paydo bo'ladigan tugmalar to'plami. Foydalanuvchi tugmani bosganda, u tugmaning matni chatga oddiy xabar sifatida yuboriladi. Ya'ni Profil tugmasini bosish β qo'lda Profil deb yozib jo'natish bilan bir xil. Sizning bot uchun bu shunchaki message:text update'i bo'lib keladi va siz uni bot.hears("Profil") yoki bot.on("message:text") bilan ushlaysiz.
Inline klaviatura β aniq bir xabarning ostiga yopishgan tugmalar. Bosilganda chatga hech qanday matn ketmaydi; uning o'rniga Telegram sizning bot'ingizga maxsus callback_query update'ini yuboradi, ichida siz oldindan belgilagan callback_data qiymati bo'ladi. Foydalanuvchi nima bosganini ko'rmaydi β chat tarixi toza qoladi.
Qisqa qoida:
| Reply klaviatura | Inline klaviatura | |
|---|---|---|
| Qayerda turadi | Yozish maydoni o'rnida | Xabar ostida |
| Bosilganda | Tugma matni chatga yuboriladi | callback_query (matn ko'rinmaydi) |
| Bot qanday ushlaydi | bot.hears / message:text |
bot.callbackQuery |
| Quruvchi | new Keyboard() |
new InlineKeyboard() |
| Qachon | Doimiy menyu, raqam/kontakt so'rash | Tasdiq, ovoz berish, sahifalash, sozlama |
Eslatma: Inline tugmaga javob berishning to'liq mexanizmi β
callback_queryni ushlash,ctx.answerCallbackQuery(), xabarni tahrirlash, sahifalash β keyingi 07-bobning asosiy mavzusi. Bu bobda biz tugmalarni qurishni o'rganamiz; ularni bosilganda nima qilishni 07-bobda chuqurlashtiramiz.Anti-eskirish: Internetda Telegraf'ning
Markup.keyboard(...)/Markup.inlineKeyboard(...)yokinode-telegram-bot-apining xom{ reply_markup: { keyboard: [[...]] } }obyektlarini ko'p uchratasiz. grammY'da bunday emas β bizKeyboard/InlineKeyboardquruvchi sinflaridan foydalanamiz. Ular oxir-oqibat o'sha standart Telegram obyektini quradi, lekin zanjir (chain) usuli ancha o'qiluvchan.
Reply klaviatura: Keyboard quruvchi¶
Keyboard sinfini grammydan import qilamiz va zanjir bilan tugma qo'shamiz:
import { Bot, Keyboard } from "grammy";
const bot = new Bot(process.env.BOT_TOKEN);
bot.command("menyu", (ctx) => {
const kb = new Keyboard()
.text("Profil").text("Sozlamalar").row()
.text("Yordam")
.resized();
return ctx.reply("Menyuni tanlang:", { reply_markup: kb });
});
bot.start(); // illustrativ: jonli polling token + internet talab qiladi
Bu yerda muhim ikki metod:
.text("...")β yangi matnli tugma qo'shadi, joriy qatorga..row()β "qator uzilishi". Shundan keyingi tugmalar yangi qatorga tushadi.
Yuqoridagi misolda Profil va Sozlamalar bitta qatorda yonma-yon turadi, Yordam esa pastdagi alohida qatorda. Bu aynan .text().text().row().text() zanjirining natijasi.
Quruvchi qanday tuzilma quradi (offline tasdiqlangan)¶
Keyboard aslida keyboard degan ichki ikki o'lchovli massivni to'ldiradi. Buni node'da to'g'ridan-to'g'ri ko'rsak bo'ladi (_verify_06.mjs'dan):
const kb = new Keyboard()
.text("Menyu").row()
.text("Yordam").resized().oneTime();
console.log(kb.keyboard.length); // 2 (ikki qator)
console.log(kb.keyboard[0][0].text); // "Menyu"
console.log(kb.keyboard[1][0].text); // "Yordam"
console.log(kb.resize_keyboard); // true
console.log(kb.one_time_keyboard); // true
Demak kb β bu oddiy obyekt: keyboard massivi + bir nechta bayroq (resize_keyboard, one_time_keyboard, ...). ctx.reply(..., { reply_markup: kb }) deganda grammY shu obyektni Telegram'ga uzatadi.
Sozlamalar: .resized(), .oneTime(), .persistent(), .placeholder()¶
Keyboardning ko'rinishini boshqaradigan zanjir metodlari:
| Metod | Nima qiladi | Mos bayroq |
|---|---|---|
.resized() |
Klaviaturani tugmalar hajmiga moslab kichraytiradi (deyarli har doim kerak) | resize_keyboard: true |
.oneTime() |
Bir marta bosilgach klaviatura yashiriladi | one_time_keyboard: true |
.persistent() |
Klaviatura har doim ko'rinib turadi (yashirin emas) | is_persistent: true |
.placeholder("...") |
Yozish maydonida kulrang maslahat matn | input_field_placeholder |
Diqqat:
.resized()ni deyarli har doim chaqiring. Aks holda Telegram klaviaturani butun ekran balandligida ko'rsatadi va ikki-uchta tugma uchun yarim ekranni egallaydi β chiroyli emas.
Misol β to'liq sozlangan klaviatura:
const kb = new Keyboard()
.text("Buyurtmalarim").text("Savatcha").row()
.text("Yordam")
.resized()
.persistent()
.placeholder("Menyudan tanlang yoki yozing...");
await ctx.reply("Asosiy menyu:", { reply_markup: kb });
Maxsus reply tugmalar: kontakt, joylashuv, Mini App¶
Reply klaviatura faqat matn yuborish bilan cheklanmaydi β telefon raqami yoki joylashuvni bir tugma bilan so'rashi mumkin (faqat shaxsiy chatda ishlaydi):
const kb = new Keyboard()
.requestContact("π Raqamni ulashish").row()
.requestLocation("π Joylashuvni yuborish").row()
.webApp("π Ilovani ochish", "https://example.com")
.resized();
await ctx.reply("Davom etish uchun ma'lumot ulashing:", { reply_markup: kb });
.requestContact(text)β bosilganda foydalanuvchidan ruxsat so'raladi va uning telefon raqamimessage.contactichida keladi..requestLocation(text)β joriy joylashuvmessage.locationichida keladi..webApp(text, url)β Telegram Mini Appni ochadi (23-26 boblar mavzusi).
Bularning tuzilmasi ham offline tasdiqlangan:
const kb = new Keyboard()
.requestContact("Raqam").row()
.requestLocation("Joylashuv");
console.log(kb.keyboard[0][0].request_contact); // true
console.log(kb.keyboard[1][0].request_location); // true
Eslatma: Kontakt yoki joylashuv kelganda uni
bot.on("message:contact")/bot.on("message:location")bilan ushlaysiz (filter query'lar β 04-bobda ko'rdik). Tugmaning o'zi faqat so'rovni boshlaydi; ma'lumotni yana handler ushlaydi.
Klaviaturani olib tashlash¶
Reply klaviatura β "yopishqoq": bir marta ko'rsatilsa, foydalanuvchi har gal yozganda turaveradi (agar .oneTime() qo'ymagan bo'lsangiz). Uni dasturiy ravishda olib tashlash uchun maxsus obyekt yuboramiz:
bot.command("yashir", (ctx) =>
ctx.reply("Klaviatura olib tashlandi.", {
reply_markup: { remove_keyboard: true },
})
);
Bu yerda quruvchi yo'q β bu shunchaki { remove_keyboard: true } obyekti. grammY uni o'zgartirmasdan Telegram'ga uzatadi (offline tasdiqlangan: payload aynan shu obyekt bo'ldi).
Diqqat:
remove_keyboardfaqat reply klaviaturani olib tashlaydi. Inline klaviaturani olib tashlash boshqacha β xabarni tahrirlabreply_markupni bo'shatasiz (ctx.editMessageReplyMarkup()yokireply_markup: undefinedbilan tahrirlash). Buni 07-bobda ko'ramiz.
Inline klaviatura: InlineKeyboard quruvchi¶
Inline klaviatura xuddi shu zanjir uslubida quriladi, lekin InlineKeyboard sinfi bilan va tugma turlari boshqacha:
import { Bot, InlineKeyboard } from "grammy";
const bot = new Bot(process.env.BOT_TOKEN);
bot.command("ovoz", (ctx) => {
const ik = new InlineKeyboard()
.text("π Ha", "ovoz:ha")
.text("π Yo'q", "ovoz:yoq");
return ctx.reply("Bu g'oya sizga yoqdimi?", { reply_markup: ik });
});
Bu yerda .text(matn, data) ikki argument oladi:
- birinchi β tugmada ko'rinadigan matn (
π Ha), - ikkinchi β
callback_data, ya'ni tugma bosilganda bot'ga keladigan yashirin qiymat (ovoz:ha).
Foydalanuvchi π Hani bosadi, lekin chatga hech narsa yozilmaydi β bot esa callback_query ichida ovoz:hani oladi.
Diqqat: Inline
.text()reply.text()dan farq qiladi! Reply'da ikkinchi argument yo'q (tugma matni o'zi chatga ketadi). Inline'da ikkinchi argument βcallback_data. Buni adashtirmang.
Agar callback_datani tashlab ketsangiz, grammY uni ko'rinadigan matnga teng qilib qo'yadi:
const ik = new InlineKeyboard().text("Tugma");
console.log(ik.inline_keyboard[0][0].callback_data); // "Tugma"
Inline tugma turlari¶
InlineKeyboardda bir nechta tugma turi bor, va faqat bittasi bot'ga update yuboradi:
const ik = new InlineKeyboard()
.text("Ovoz berish", "vote:1").row() // -> callback_query (BOT ushlaydi)
.url("Sayt", "https://grammy.dev").row() // -> brauzerda havola ochadi
.webApp("Mini App", "https://example.com").row() // -> Telegram ichida Mini App
.switchInline("Do'stga ulashish"); // -> chat tanlash + inline so'rov
| Metod | Bosilganda | Bot update oladimi? |
|---|---|---|
.text(matn, data) |
callback_query yuboradi |
Ha (bot.callbackQuery) |
.url(matn, url) |
havolani ochadi | Yo'q |
.webApp(matn, url) |
Telegram ichida Mini App ochadi | Yo'q (App o'zi sendData qilishi mumkin β 23-bob) |
.switchInline(matn, query?) |
chat tanlatib inline so'rov boshlaydi | Yo'q (inline_query keladi β 07-bob) |
Eslatma: Bu juda muhim gotcha. Faqat
.text()(callback) tugmasi bot'ga to'g'ridan-to'g'ri update yuboradi..url()shunchaki tashqi havola β bot hech narsa bilmaydi. Boshlovchilar ko'pincha.url()tugmasigabot.callbackQueryyozib qo'yib, "nega ishlamayapti?" deb hayron bo'lishadi.
Inline tuzilma ham offline tasdiqlangan:
const ik = new InlineKeyboard()
.text("Ha", "yes").text("Yo'q", "no").row()
.url("Sayt", "https://grammy.dev")
.webApp("Ochish", "https://example.com")
.switchInline("Ulashish");
console.log(ik.inline_keyboard.length); // 2 (qator)
console.log(ik.inline_keyboard[0][0].callback_data); // "yes"
console.log(ik.inline_keyboard[1][0].url); // "https://grammy.dev"
console.log(ik.inline_keyboard[1][1].web_app.url); // "https://example.com"
console.log(ik.inline_keyboard[1][2].switch_inline_query); // ""
Eslatma: Kuzatganmisiz β inline tugmalar
inline_keyboardmassivida, reply tugmalar esakeyboardmassivida saqlanadi. Telegram API'sida bu ikki maydon turlicha nomlanadi va grammY shu farqni saqlaydi. Shuning uchunKeyboardvaInlineKeyboardquruvchilari almashtirilmaydi.
Inline tugmani bosilganda javob berish (qisqa ko'rinish)¶
Bu bobda chuqurlashmaymiz (07-bobda), lekin to'liq tasvir bo'lishi uchun callback handler shunaqa ulanadi:
bot.command("ovoz", (ctx) =>
ctx.reply("Yoqdimi?", {
reply_markup: new InlineKeyboard().text("Ha", "ovoz:ha").text("Yo'q", "ovoz:yoq"),
})
);
// Aniq callback_data bo'yicha:
bot.callbackQuery("ovoz:ha", (ctx) => ctx.answerCallbackQuery({ text: "Rahmat!" }));
bot.callbackQuery("ovoz:yoq", (ctx) => ctx.answerCallbackQuery({ text: "Eslab qoldik." }));
Bu naqsh offline tasdiqlangan: /ovozdan keyin "Ha" tugmasining callback_query'sini yuborganimizda answerCallbackQuery chaqirildi va matni "Ovozingiz qabul qilindi!" bo'ldi.
Diqqat:
ctx.answerCallbackQuery()ni har doim chaqiring. Aks holda Telegram klientida tugma ustida aylanma "yuklanish" belgisi bir necha soniya qotib qoladi β foydalanuvchi bot osilgan deb o'ylaydi. Tafsilot β 07-bob.
Tugmalarni qatorlar va ustunlarga joylashtirish¶
Klaviatura tartibi β bu ikki o'lchovli massiv: tashqi massiv qatorlar, ichki massivlar har qatordagi tugmalar. Ikki yo'l bilan boshqaramiz.
Yo'l 1 β qo'lda .row() bilan¶
Eng aniq usul: qaerda qator uzilishi kerakligini o'zingiz aytasiz.
const ik = new InlineKeyboard()
.text("1", "n:1").text("2", "n:2").text("3", "n:3").row()
.text("4", "n:4").text("5", "n:5").text("6", "n:6").row()
.text("Bekor qilish", "cancel");
// natija: [3 tugma] [3 tugma] [1 tugma]
Yo'l 2 β .toFlowed(ustunlar) bilan (grammY'ning .adjust ekvivalenti)¶
aiogram (Python ekvivalenti) bilan tanish bo'lsangiz, u yerda builder.adjust(2) degan metod tugmalarni avtomatik ustunlarga taqsimlaydi. grammY'da bu .toFlowed(columns) deb ataladi β u barcha tugmalarni olib, berilgan ustunlar soni bo'yicha yangi klaviaturaga qayta oqizadi:
const ik = new InlineKeyboard();
for (let i = 1; i <= 7; i++) ik.text(String(i), `n:${i}`);
const tartiblangan = ik.toFlowed(3);
// 7 tugma -> 3 ustun -> [3, 3, 1] qatorlar
Offline tasdiqlangan:
Eslatma:
.toFlowed()yangi klaviatura qaytaradi (aslikni o'zgartirmaydi). Shuning uchun natijaniconst tartiblangan = ...ga oling.Keyboardda ham xuddi shu.toFlowed()bor.
Dinamik klaviatura: massivdan qurish¶
Real botlarda tugmalar ko'pincha ma'lumotlar bazasidan yoki massivdan keladi β masalan, tovarlar ro'yxati, sahifa raqamlari, til tanlovi. Sikl bilan quramiz.
Misol β tovarlar ro'yxatini 2 ustunga¶
const tovarlar = ["Olma", "Anor", "Banan", "Uzum", "Shaftoli"];
const ik = new InlineKeyboard();
tovarlar.forEach((nom, i) => {
ik.text(nom, `tovar:${i}`);
if ((i + 1) % 2 === 0) ik.row(); // har 2 tadan keyin yangi qator
});
// 5 tugma -> [2, 2, 1]
Yoki yanada toza β barcha tugmalarni qo'shib, oxirida .toFlowed(2):
const ik = new InlineKeyboard();
tovarlar.forEach((nom, i) => ik.text(nom, `tovar:${i}`));
const tayyor = ik.toFlowed(2); // -> [2, 2, 1]
Ikkala usul ham offline tasdiqlangan ([2,2,1] qatorlar).
Til tanlovi misoli (lotinlashtirilgan)¶
const tillar = [
{ kod: "uz", nom: "πΊπΏ O'zbekcha" },
{ kod: "ru", nom: "π·πΊ Ruscha" },
{ kod: "en", nom: "π¬π§ Inglizcha" },
];
const ik = new InlineKeyboard();
for (const t of tillar) ik.text(t.nom, `til:${t.kod}`).row();
await ctx.reply("Tilni tanlang:", { reply_markup: ik });
Diqqat: Tugma matnida xorijiy yozuvni ham lotinlashtiring β kitob qoidasi. Til tanlovida "Ruscha" deb yozing, asl kirillcha yozuvda emas. Tugma matni β bu sizning bot interfeysingiz, uni o'zbek lotin alifbosida yozing.
Statik tugmalardan: InlineKeyboard.from(...)¶
Ba'zan tugmalar massivini alohida tayyorlab, keyin klaviaturaga aylantirish qulay. Har bir tugma turining statik ko'rinishi bor:
const grid = [
[InlineKeyboard.text("A", "a"), InlineKeyboard.text("B", "b")],
[InlineKeyboard.url("Sayt", "https://grammy.dev")],
];
const ik = InlineKeyboard.from(grid);
// ik.inline_keyboard[0][1].callback_data === "b"
// ik.inline_keyboard[1][0].url === "https://grammy.dev"
Offline tasdiqlangan. Keyboard.from(...) ham xuddi shunday ishlaydi.
callback_data dizayni β qisqa va tejamli¶
Inline .text() tugmasining callback_data'si Telegram tomonidan 64 baytgacha cheklangan. Bu xat β chuqur 07-bobda, lekin asosiy qoidalar shu yerda:
- Qisqa kalit:qiymat shakli β
page:2,tovar:14,ovoz:ha. Ikki nuqta (:) ajratuvchi sifatida qulay, keyindata.split(":")bilan ajratasiz. - 64 bayt β belgilar emas, baytlar. Emoji va kirill (agar bo'lsa) bir nechta bayt egallaydi. ASCII harf β 1 bayt.
- Ichiga ma'lumot tiqmang.
user_profile_settings_section_email_v=1β yomon. Buning o'rniga qisqa kalit (pset:email) yuboring, qolganini bot tomonida (sessiya/DB) saqlang.
Bayt o'lchamini tekshirish (offline tasdiqlangan):
Diqqat: 64 baytdan oshsa, Telegram klaviaturani butunlay rad etadi (
BUTTON_DATA_INVALIDxatosi) β ba'zan jim, ba'zan xato bilan. Shuning uchuncallback_datani qisqa tuting. Buni 07-bobda strukturali (masalan@grammyjs/...yoki qo'ldaJSONo'rniga qisqa kodlar) tarzda hal qilamiz.
Tez-tez uchraydigan xatolar¶
| Xato | Sabab | Yechim |
|---|---|---|
| Tugma umuman ko'rinmaydi | reply_markup berilmagan |
ctx.reply(matn, { reply_markup: kb }) β ikkinchi argumentni unutmang |
| Inline tugma bosilsa hech narsa bo'lmaydi | bot.callbackQuery(...) handleri yo'q |
callback_dataga mos bot.callbackQuery("...") qo'shing |
.url() tugmasi callback bermaydi |
url tugmasi update yubormaydi | Bot javob berishi kerak bo'lsa .text(matn, data) ishlating |
| Klaviatura juda baland | .resized() chaqirilmagan |
Keyboardga .resized() qo'shing |
| Reply tugma "ishlamaydi" | bot.callbackQuery bilan ushlamoqchi bo'lgansiz |
Reply tugma matn yuboradi β bot.hears("...") bilan ushlang |
Inline .text("X") β callback_data topilmadi |
data argumentini bermagansiz, u matnga teng | Aniq data bering: .text("X", "x:1") |
BUTTON_DATA_INVALID |
callback_data 64 baytdan oshgan |
Qisqa kalit yuboring, ma'lumotni serverda saqlang |
| Klaviatura eski xabarda turaveradi | reply klaviatura yopishqoq | { remove_keyboard: true } yoki .oneTime() |
.toFlowed() natija bermadi |
u yangi klaviatura qaytaradi, asilni o'zgartirmaydi | Natijani o'zgaruvchiga oling: const k = ik.toFlowed(2) |
Mashqlar¶
Quyidagi mashqlarning ko'pi offline tekshiriladi. Naqsh _verify_02.mjs / smoke.mjs dagi bilan bir xil: makeBot() soxta bot quradi, bot.api.config.use(...) transformeri chiqayotgan reply_markup payload'ini ushlaydi. Klaviatura tuzilmasini esa to'g'ridan-to'g'ri kb.keyboard / ik.inline_keyboard orqali assert qilamiz. Buyruq update'iga entities:[{type:"bot_command",...}] qo'shishni unutmang.
Oson¶
- Ha/Yo'q inline.
new InlineKeyboard().text("Ha","ha").text("Yo'q","yoq")quring vaik.inline_keyboard[0]ikki tugmali,callback_datalari"ha"/"yoq"ekaniniassertqiling. - Uch qatorli reply menyu.
Profil,Sozlamalar,Yordamtugmalarini har biri alohida qatorda bo'ladiganKeyboardquring (.text().row()).kb.keyboard.length === 3ekanini tekshiring. .resized()bayrog'i.Keyboardga.resized()qo'shgachkb.resize_keyboard === true, qo'shmagandaundefinedekanini ikki holatda tekshiring.- Klaviaturani olib tashlash.
/yashirbuyrug'iga{ remove_keyboard: true }yuboradigan handler yozing va transformer payload'idareply_markup.remove_keyboard === trueekaniniassertqiling.
O'rta¶
- Inline reply_markup payload'i.
/ovozbuyrug'iga ikki inline tugmali xabar yuboruvchi handler yozing. Transformer bilan ushlab,payload.reply_markup.inline_keyboard[0][1].callback_datato'g'ri ekanini tekshiring. .toFlowed(2)bilan tartiblash. 5 ta tugmaliInlineKeyboardquring,.toFlowed(2)qiling va qatorlar uzunligi[2,2,1]ekaniniassertqiling.- Dinamik til menyusi.
["uz","ru","en"]massividan har biri alohida qatorda bo'ladigan inline klaviatura quruvchi funksiya yozing;callback_datalartil:uzko'rinishida ekanini tekshiring. .url()callback yubormasligi..url("Sayt","https://grammy.dev")tugmali xabargabot.callbackQuery(...)handler ishlamasligini ko'rsating: callback handler hech qachon chaqirilmaganini (callsbo'sh) tekshiring (chunki url tugma callback yubormaydi β siz callback update yubormaysiz).callback_databayt cheklovi. Berilgan satrningTextEncoder().encode(s).length <= 64ekanini tekshiruvchi yordamchimosKeladi(s)funksiya yozing va"page:2"-> true, 70 ta"x"-> false ekaniniassertqiling.
Qiyin¶
- Sahifalash klaviaturasi.
pageKeyboard(joriy, jami)funksiya yozing: agarjoriy > 1bo'lsaβ¬ οΈ(page:{joriy-1}), o'rtada{joriy}/{jami}matnli (noopdata),joriy < jamibo'lsaβ‘οΈ(page:{joriy+1}) tugma.pageKeyboard(1,3)da chap tugma yo'qligini,pageKeyboard(2,3)da uchala tugma borliginiassertqiling. - Reply tugma matnini marshrutlash.
Profil/Yordamreply tugmalari menyusini ko'rsatadigan/menyuva ularning matnini ushlaydiganbot.hears("Profil")/bot.hears("Yordam")yozing.Profilmatnli update yuborganda to'g'ri javob kelishini transformer bilan tekshiring (reply tugma = matn). - Ovoz berish hisoblagichi. Inline
π/πtugmali post yuboring;bot.callbackQuery("vote:up")/bot.callbackQuery("vote:down")ovozlarniMapda sanasin vaanswerCallbackQuerymatnida joriy hisobni qaytarsin. Ikkiup+ birdowncallback yuborib, oxirgi javob matnida2/1aks etganiniassertqiling. KeyboardvaInlineKeyboardfarqini isbotlash. Bitta funksiyada bir xil tugmalardan ikkalasini quring vaKeyboarddakeyboardmaydoni,InlineKeyboarddainline_keyboardmaydoni borligini (va ikkinchisidacallback_databorligini)assertbilan ko'rsating.
Yechimlar
Hammasini $env:TEMP\grammy-probe ichida .mjs fayl sifatida saqlab node fayl.mjs bilan ishga tushiring. Umumiy yordamchilar (makeBot, mkText) _verify_06.mjsdagi bilan bir xil β bu yerda qaytarmaymiz.
1-mashq yechimi¶
import { InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
const ik = new InlineKeyboard().text("Ha", "ha").text("Yo'q", "yoq");
assert.equal(ik.inline_keyboard.length, 1);
assert.equal(ik.inline_keyboard[0].length, 2);
assert.equal(ik.inline_keyboard[0][0].callback_data, "ha");
assert.equal(ik.inline_keyboard[0][1].callback_data, "yoq");
console.log("1-mashq: OK");
.text().text() data argumentsiz qator uzilishisiz β ikkala tugma bitta qatorda.
2-mashq yechimi¶
import { Keyboard } from "grammy";
import assert from "node:assert/strict";
const kb = new Keyboard()
.text("Profil").row()
.text("Sozlamalar").row()
.text("Yordam");
assert.equal(kb.keyboard.length, 3);
assert.deepEqual(kb.keyboard.map((r) => r[0].text), ["Profil", "Sozlamalar", "Yordam"]);
console.log("2-mashq: OK");
Har .text()dan keyin .row() β har bir tugma alohida qatorga tushadi.
3-mashq yechimi¶
import { Keyboard } from "grammy";
import assert from "node:assert/strict";
const bilan = new Keyboard().text("A").resized();
const bilansiz = new Keyboard().text("A");
assert.equal(bilan.resize_keyboard, true);
assert.equal(bilansiz.resize_keyboard, undefined);
console.log("3-mashq: OK");
.resized() faqat resize_keyboard bayrog'ini o'rnatadi; chaqirilmasa maydon umuman yo'q (undefined).
4-mashq yechimi¶
// makeBot, mkText β _verify_06.mjs dan
const { bot, calls } = makeBot();
bot.command("yashir", (ctx) =>
ctx.reply("Olib tashlandi", { reply_markup: { remove_keyboard: true } }));
await bot.handleUpdate(mkText("/yashir", 1));
const c = calls.find((x) => x.method === "sendMessage");
assert.deepEqual(c.payload.reply_markup, { remove_keyboard: true });
console.log("4-mashq: OK");
remove_keyboard β quruvchi emas, oddiy obyekt; grammY uni o'zgartirmasdan uzatadi.
5-mashq yechimi¶
import { InlineKeyboard } from "grammy";
// makeBot, mkText β _verify_06.mjs dan
const { bot, calls } = makeBot();
bot.command("ovoz", (ctx) =>
ctx.reply("Ovoz bering:", {
reply_markup: new InlineKeyboard().text("Ha", "v:ha").text("Yo'q", "v:yoq"),
}));
await bot.handleUpdate(mkText("/ovoz", 1));
const c = calls.find((x) => x.method === "sendMessage");
assert.equal(c.payload.reply_markup.inline_keyboard[0][0].callback_data, "v:ha");
assert.equal(c.payload.reply_markup.inline_keyboard[0][1].callback_data, "v:yoq");
console.log("5-mashq: OK");
Transformer chiqayotgan sendMessage payload'ini ushlaydi β reply_markup ichida quruvchi qurgan inline_keyboard massivi turadi.
6-mashq yechimi¶
import { InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
const ik = new InlineKeyboard();
for (let i = 1; i <= 5; i++) ik.text(String(i), `n:${i}`);
const flowed = ik.toFlowed(2);
assert.deepEqual(flowed.inline_keyboard.map((r) => r.length), [2, 2, 1]);
console.log("6-mashq: OK");
.toFlowed(2) 5 tugmani 2 ustunga oqizadi: [2,2,1]. Asil ik o'zgarmaydi.
7-mashq yechimi¶
import { InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
function tilMenyusi(kodlar) {
const ik = new InlineKeyboard();
for (const k of kodlar) ik.text(k.toUpperCase(), `til:${k}`).row();
return ik;
}
const ik = tilMenyusi(["uz", "ru", "en"]);
assert.equal(ik.inline_keyboard.length, 3);
assert.equal(ik.inline_keyboard[0][0].callback_data, "til:uz");
assert.equal(ik.inline_keyboard[2][0].callback_data, "til:en");
console.log("7-mashq: OK");
Har til uchun .text(...).row() β har biri alohida qatorda.
8-mashq yechimi¶
import { InlineKeyboard } from "grammy";
// makeBot, mkText β _verify_06.mjs dan
const { bot, calls } = makeBot();
bot.command("sayt", (ctx) =>
ctx.reply("Havola:", {
reply_markup: new InlineKeyboard().url("Sayt", "https://grammy.dev"),
}));
// url tugma callback yubormaydi -> biz callback update YUBORMAYMIZ.
bot.callbackQuery(/.*/, (ctx) => ctx.answerCallbackQuery({ text: "Bu hech qachon" }));
await bot.handleUpdate(mkText("/sayt", 1));
// Faqat sendMessage bo'ldi, answerCallbackQuery yo'q:
assert.ok(calls.every((c) => c.method !== "answerCallbackQuery"));
console.log("8-mashq: OK (url tugma callback yubormaydi)");
.url() tugmasi bot'ga update yubormaydi β shuning uchun callback handler hech qachon ishga tushmaydi.
9-mashq yechimi¶
import assert from "node:assert/strict";
function mosKeladi(s) {
return new TextEncoder().encode(s).length <= 64;
}
assert.equal(mosKeladi("page:2"), true);
assert.equal(mosKeladi("x".repeat(70)), false);
console.log("9-mashq: OK");
callback_data cheklovi baytlarda β TextEncoder UTF-8 baytlarini sanaydi.
10-mashq yechimi¶
import { InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
function pageKeyboard(joriy, jami) {
const ik = new InlineKeyboard();
if (joriy > 1) ik.text("β¬
οΈ", `page:${joriy - 1}`);
ik.text(`${joriy}/${jami}`, "noop");
if (joriy < jami) ik.text("β‘οΈ", `page:${joriy + 1}`);
return ik;
}
const p1 = pageKeyboard(1, 3);
assert.equal(p1.inline_keyboard[0].length, 2); // chap yo'q: [hisob, β‘οΈ]
assert.equal(p1.inline_keyboard[0][0].callback_data, "noop");
const p2 = pageKeyboard(2, 3);
assert.equal(p2.inline_keyboard[0].length, 3); // [β¬
οΈ, hisob, β‘οΈ]
assert.equal(p2.inline_keyboard[0][0].callback_data, "page:1");
assert.equal(p2.inline_keyboard[0][2].callback_data, "page:3");
console.log("10-mashq: OK");
Shartli (if) tugma qo'shish β sahifaning chetida o'q yo'qligini ta'minlaydi. noop β bosib bo'lmaydigan "indikator" tugma (07-bobda answerCallbackQuery bilan jim qoldiriladi).
11-mashq yechimi¶
import { Keyboard } from "grammy";
// makeBot, mkText β _verify_06.mjs dan
const { bot, calls } = makeBot();
bot.command("menyu", (ctx) =>
ctx.reply("Menyu:", {
reply_markup: new Keyboard().text("Profil").text("Yordam").resized(),
}));
bot.hears("Profil", (ctx) => ctx.reply("Sizning profilingiz"));
bot.hears("Yordam", (ctx) => ctx.reply("Yordam: /menyu"));
await bot.handleUpdate(mkText("/menyu", 1));
await bot.handleUpdate(mkText("Profil", 2)); // reply tugma = "Profil" MATNI
const texts = calls.filter((c) => c.method === "sendMessage").map((c) => c.payload.text);
import assert from "node:assert/strict";
assert.deepEqual(texts, ["Menyu:", "Sizning profilingiz"]);
console.log("11-mashq: OK");
Reply tugma bosilishi = o'sha matnli oddiy xabar. Shuning uchun uni bot.hears ushlaydi, bot.callbackQuery emas.
12-mashq yechimi¶
import { InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
// makeBot β _verify_06.mjs dan
const { bot, calls } = makeBot();
const ovozlar = { up: 0, down: 0 };
bot.command("post", (ctx) =>
ctx.reply("Yoqdimi?", {
reply_markup: new InlineKeyboard().text("π", "vote:up").text("π", "vote:down"),
}));
bot.callbackQuery("vote:up", (ctx) => {
ovozlar.up++;
return ctx.answerCallbackQuery({ text: `π ${ovozlar.up} / π ${ovozlar.down}` });
});
bot.callbackQuery("vote:down", (ctx) => {
ovozlar.down++;
return ctx.answerCallbackQuery({ text: `π ${ovozlar.up} / π ${ovozlar.down}` });
});
function cbUpdate(data, id) {
return {
update_id: id,
callback_query: {
id: "cb" + id, from: { id: 777, is_bot: false, first_name: "Ali" },
chat_instance: "x", data,
message: { message_id: 1, date: 0, chat: { id: 777, type: "private" } },
},
};
}
await bot.handleUpdate(cbUpdate("vote:up", 1));
await bot.handleUpdate(cbUpdate("vote:up", 2));
await bot.handleUpdate(cbUpdate("vote:down", 3));
const oxirgi = calls.filter((c) => c.method === "answerCallbackQuery").at(-1);
assert.equal(oxirgi.payload.text, "π 2 / π 1");
console.log("12-mashq: OK");
Har callback hisoblagichni oshiradi va joriy natijani answerCallbackQuery matnida qaytaradi. (Real botda hisobni xabar matniga editMessageText bilan yozasiz β 07-bob.)
13-mashq yechimi¶
import { Keyboard, InlineKeyboard } from "grammy";
import assert from "node:assert/strict";
function ikkalasi(matn) {
const reply = new Keyboard().text(matn).resized();
const inline = new InlineKeyboard().text(matn, "data:1");
return { reply, inline };
}
const { reply, inline } = ikkalasi("Bosing");
// Reply'da 'keyboard' maydoni, callback_data YO'Q:
assert.ok(Array.isArray(reply.keyboard));
assert.equal(reply.inline_keyboard, undefined);
assert.equal(reply.keyboard[0][0].text, "Bosing");
// Inline'da 'inline_keyboard' maydoni, callback_data BOR:
assert.ok(Array.isArray(inline.inline_keyboard));
assert.equal(inline.keyboard, undefined);
assert.equal(inline.inline_keyboard[0][0].callback_data, "data:1");
console.log("13-mashq: OK");
Bu farqning ildizi: Telegram API'sida reply klaviatura keyboard maydonida, inline esa inline_keyboard maydonida β va faqat inline tugmalar callback_data saqlaydi.
Yakunda¶
Bu bobda klaviaturalarning ikki olamini ajratdik:
- Reply klaviatura (
Keyboard) β yozish maydonini almashtiradi, bosilganda chatga matn yuboradi,bot.hearsbilan ushlaysiz..text(),.row(),.resized(),.oneTime(),.persistent(),.placeholder(), va.requestContact()/.requestLocation()/.webApp(). - Inline klaviatura (
InlineKeyboard) β xabar ostida turadi, faqat.text(matn, data)tugmasi bot'gacallback_queryyuboradi (.url()/.webApp()/.switchInline()yubormaydi).bot.callbackQuerybilan ushlaysiz. - Tugmalarni
.row()bilan qo'lda yoki.toFlowed(ustunlar)bilan avtomatik qatorlash; massivdan sikl bilan dinamik qurish. callback_dataβ qisqakalit:qiymat, 64 bayt chegarasida.
Hamma quruvchi va handler tuzilmasi offline tasdiqlandi (14/14). Keyingi qadam β inline tugma bosilganda nima qilish: callback_queryni to'liq ushlash, answerCallbackQuery, xabarni joyida tahrirlash, sahifalash va inline rejim. Bu β 07-bobning mavzusi.
β¬ οΈ Oldingi: 05 β Xabar yuborish, formatlash va media Β· π README Β· Keyingi: 07 β Callback query va inline rejim β‘οΈ