06 β Klaviaturalar: reply va inline¶
β¬ οΈ Oldingi: 05 β Xabar yuborish, formatlash va media Β· π README Β· Keyingi: 07 β Callback query va inline rejim β‘οΈ
Bu bobda: botingiz endi xabar yubora oladi. Lekin foydalanuvchini har safar buyruq yozdirib o'tirish noqulay β unga tugma kerak. Telegram ikki xil klaviatura beradi: reply klaviatura (telefon klaviaturasi o'rnida paydo bo'ladigan tugmalar) va inline klaviatura (aynan xabarning ostiga yopishib turadigan tugmalar). Biz ularni
ReplyKeyboardBuildervaInlineKeyboardBuilderquruvchilari bilan yasaymiz. O'rganasiz: oddiy matnli tugmalar,resize_keyboard(tugmalarni kichraytirish),one_time_keyboard(bir martalik),input_field_placeholder(kirish maydoni ko'rsatmasi), telefon raqami so'rash (request_contact) va joylashuv so'rash (request_location); inline tugmalardacallback_datavaurl; tugmalarni qatorlarga joylashtiruvchibuilder.adjust(); reply klaviaturaniReplyKeyboardRemovebilan o'chirish;ForceReplybilan majburiy javob so'rash; va eng muhimi β inline yoki reply qachon ishlatish kerakligi. Inline tugma bosilganda keladigancallback_queryni shu yerda kirish darajasida ko'ramiz; chuqurroq qism β keyingi 07-bobda.Halol eslatma (verifikatsiya): quyidagi klaviatura quruvchilari (
ReplyKeyboardBuilder/InlineKeyboardBuilderva ularningas_markup()natijasi), handler routing (soxtaUpdateni dispatcher'gafeed_updatebilan uzatib), hamdaReplyKeyboardRemove/ForceReplyobyektlari token va internetsiz OFFLINE tekshirildi β API chaqiruvlari soxta sessiya bilan ushlandi, real HTTP ketmadi. Tugmalarning ekranga real chiqishi va bosilganda javob qaytishi esa jonli Telegram (BotFather token + internet) talab qiladi β bunday joylar matnda "illustrativ" deb belgilangan, soxta "ishladi / chiqdi" yozilmagan. Versiyalar: aiogram 3.28, Python 3.14, aiohttp 3.13.
6.1. Nega tugmalar kerak?¶
Avvalgi boblarda foydalanuvchi botga /start, /help kabi buyruqlarni qo'lda yozardi. Bu dasturchi uchun qulay, lekin oddiy odam uchun emas β u qaysi buyruqlar borligini eslab o'tirmaydi va bittasini xato yozsa, bot "tushunmadim" deydi. Tugma esa ko'rinib turadi: bosing va tamom. Tugmalar botni ilovaga o'xshatadi, foydalanuvchini "to'g'ri yo'l"ga soladi va xatolarni kamaytiradi.
Telegram'da ikki turdagi klaviatura bor, va ularni aralashtirib yubormaslik juda muhim:
| Reply klaviatura | Inline klaviatura | |
|---|---|---|
| Qayerda turadi | Telefon klaviaturasi o'rnida (input maydonida) | Aynan xabarning ostida |
| Bosilganda nima bo'ladi | Tugma matni oddiy xabar sifatida botga yuboriladi | Botga callback_query keladi (matn yuborilmaydi) |
| Bot qanday tutadi | @router.message(...) β matn bo'yicha |
@router.callback_query(...) β callback_data bo'yicha |
| Telefon/joylashuv so'rash | Mumkin (request_contact, request_location) |
Mumkin emas |
| URL ochish, to'lov tugmasi | Mumkin emas | Mumkin (url, pay) |
| Xabarni keyin tahrirlash | Mumkin emas | Mumkin (tugmalarni almashtirish) |
| Ko'rinish | Hamma xabarlarda turaveradi (o'chirilguncha) | Faqat o'sha xabarda |
Eng oddiy qoida (bobning oxirida batafsil): reply β doimiy asosiy menyu va telefon/joylashuv so'rash uchun; inline β bitta xabarga bog'liq tanlovlar (ovoz berish, sahifalash, "ha/yo'q", sozlama almashtirish) uchun.
6.2. Reply klaviatura: ReplyKeyboardBuilder¶
Reply klaviaturani builder (quruvchi) orqali yasaymiz. aiogram 3.x da to'g'ri import:
Diqqat (2.x emas, 3.x): eski darslarda
ReplyKeyboardMarkup(resize_keyboard=True).add(KeyboardButton("..."))ko'rishingiz mumkin. Bu aiogram 2.x uslubi. 3.x da hamReplyKeyboardMarkupobyektini qo'lda yasash mumkin, lekin tavsiya etilgan zamonaviy yo'l β builder. Biz shuni o'rganamiz.
Eng oddiy reply klaviatura¶
import asyncio
import os
from aiogram import Bot, Dispatcher, Router
from aiogram.filters import CommandStart
from aiogram.types import Message
from aiogram.utils.keyboard import ReplyKeyboardBuilder
router = Router()
@router.message(CommandStart())
async def start(message: Message) -> None:
builder = ReplyKeyboardBuilder()
builder.button(text="Katalog")
builder.button(text="Savatcha")
builder.button(text="Yordam")
# adjust(2) -> har qatorda 2 tadan tugma
builder.adjust(2)
await message.answer(
"Asosiy menyu:",
reply_markup=builder.as_markup(resize_keyboard=True),
)
async def main() -> None:
bot = Bot(token=os.environ["BOT_TOKEN"])
dp = Dispatcher()
dp.include_router(router)
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())
Bu yerda nima bo'ldi:
ReplyKeyboardBuilder()β bo'sh quruvchi..button(text=...)β har bir chaqiruv bitta tugma qo'shadi. Eslatma:.button()text=ni nomli argument sifatida kutadi..adjust(2)β tugmalarni qatorlarga joylaydi.2degani har qatorda 2 ta (oxirgi qator kam bo'lsa, qoladi). Buni keyin batafsil ko'ramiz..as_markup(resize_keyboard=True)β quruvchidan tayyorReplyKeyboardMarkupobyektini chiqaradi.resize_keyboard=Trueβ tugmalarni mazmuniga qarab kichraytiradi (aks holda ular ekranning yarmini egallaydi). Bu deyarli har doim kerak.reply_markup=β yasalgan klaviaturani xabar bilan birga yuboramiz.
Jonli botda bu shunday ko'rinadi:
/startyozasiz, pastda klaviatura o'rnida "Katalog / Savatcha" qatori va ostida "Yordam" tugmasi chiqadi (illustrativ β token + internet kerak).
as_markup() parametrlari¶
resize_keyboard yagona emas. To'liq foydali to'plam:
markup = builder.as_markup(
resize_keyboard=True, # tugmalarni mazmuniga moslab kichraytir
one_time_keyboard=True, # bir marta bosilgach, klaviatura yashiriniladi
input_field_placeholder="Menyudan tanlang...", # input maydonidagi kulrang yozuv
selective=False, # True bo'lsa faqat ayrim foydalanuvchilarga ko'rsatadi
)
| Parametr | Nima qiladi |
|---|---|
resize_keyboard=True |
Tugmalarni kontentga moslab kichraytiradi (UX uchun shart) |
one_time_keyboard=True |
Foydalanuvchi tugma bosgach klaviatura yig'iladi (lekin o'chmaydi β qaytarish mumkin) |
input_field_placeholder="..." |
Bo'sh kirish maydonida ko'rinadigan ko'rsatma matn |
selective=True |
Guruhda: faqat reply qilingan/eslatilgan foydalanuvchilarga ko'rsatiladi |
OFFLINE tekshirildi:
as_markup(resize_keyboard=True, one_time_keyboard=True, input_field_placeholder="...")chaqirilganda natija obyektiningmarkup.resize_keyboard,markup.one_time_keyboard,markup.input_field_placeholdermaydonlari aynan o'rnatiladi. Ya'ni bu parametrlaras_markup()orqali to'g'ridan-to'g'riReplyKeyboardMarkupga o'tadi.
6.3. Tugmalarni qatorlarga joylash: builder.adjust()¶
adjust() β bu quruvchining eng foydali metodi. U tugmalar qaysi qatorga necha tadan tushishini hal qiladi. Tugmalarni siz .button() bilan ketma-ket qo'shasiz, adjust() esa ularni qatorlarga "sindiradi".
Bitta o'lcham: adjust(N)¶
adjust(2) β har qatorda 2 tadan. Agar siz 5 ta tugma qo'shsangiz, natija: [2, 2, 1] (oxirgi qatorda 1 ta qoladi).
adjust(N) da oxirgi o'lcham qolgan barcha qatorlarga takrorlanadi. Ya'ni adjust(3) 9 ta tugmada [3, 3, 3] beradi.
Bir nechta o'lcham: adjust(a, b, c, ...)¶
Har bir son β navbatdagi qatorning kengligi. adjust(1, 2) 8 ta tugmada:
# 8 ta tugma, adjust(1, 2)
builder.adjust(1, 2)
# natija qatorlar: [1, 2, 2, 2, 1]
# -> 1-qator 1 ta, keyin OXIRGI berilgan o'lcham (2) qolgan tugmalarga takrorlanadi
OFFLINE tekshirildi: 8 ta tugma +
adjust(1, 2)-> qatorlar[1, 2, 2, 2, 1]. Demak ko'rsatilgan o'lchamlar tugagach, oxirgi son (bu yerda2) qolganlarga takrorlanadi.
Naqshni takrorlash: adjust(..., repeat=True)¶
Agar 1, 2 naqshini takrorlatmoqchi bo'lsangiz (1, 2, 1, 2, ...), repeat=True bering:
# 8 ta tugma, adjust(1, 2, repeat=True)
builder.adjust(1, 2, repeat=True)
# natija qatorlar: [1, 2, 1, 2, 1, 1]
OFFLINE tekshirildi: 8 ta tugma +
adjust(1, 2, repeat=True)-> qatorlar[1, 2, 1, 2, 1, 1]. Naqsh (1, 2) butunicha takrorlanadi, oxirgi yagona tugma bitta qatorda qoladi.
adjust chaqirmasangiz nima bo'ladi?¶
- Reply klaviaturada: agar
adjust()chaqirmasangiz, hamma tugma bitta qatorga tushadi (OFFLINE tekshirildi: 3 ta tugma ->[['A', 'B', 'C']]). Shuning uchun reply uchunadjust()deyarli har doim kerak. - Inline klaviaturada: standart holatda har qatorda 1 ta tugma bo'ladi (har biri alohida qatorda).
adjust() ni esda saqlash uchun: "men tugmalarni ketma-ket qo'shaman, adjust esa ularni qatorlarga bo'ladi".
6.4. Maxsus reply tugmalar: telefon va joylashuv so'rash¶
Reply klaviaturaning kuchli tomoni β u foydalanuvchidan kontakt (telefon raqami) yoki joylashuv so'ray oladi. Bunday tugma bosilsa, foydalanuvchiga "ulashasizmi?" so'rovi chiqadi va rozilik bersa, bot maxsus xabar oladi.
from aiogram import Router, F
from aiogram.filters import CommandStart
from aiogram.types import Message
from aiogram.utils.keyboard import ReplyKeyboardBuilder
router = Router()
@router.message(CommandStart())
async def start(message: Message) -> None:
builder = ReplyKeyboardBuilder()
builder.button(text="Telefon raqamni yuborish", request_contact=True)
builder.button(text="Joylashuvni yuborish", request_location=True)
builder.adjust(1) # har qatorda 1 ta
await message.answer(
"Ro'yxatdan o'tish uchun ma'lumotlaringizni ulashing:",
reply_markup=builder.as_markup(resize_keyboard=True),
)
# Foydalanuvchi telefon yuborsa -> message.contact to'ladi
@router.message(F.contact)
async def got_contact(message: Message) -> None:
c = message.contact
await message.answer(
f"Rahmat! Raqamingiz: {c.phone_number}\n"
f"Ism: {c.first_name}"
)
# Foydalanuvchi joylashuv yuborsa -> message.location to'ladi
@router.message(F.location)
async def got_location(message: Message) -> None:
loc = message.location
await message.answer(
f"Joylashuv qabul qilindi:\n"
f"lat={loc.latitude}, lon={loc.longitude}"
)
Muhim nuqtalar:
request_contact=Truevarequest_location=Trueβ bular.button()ga to'g'ridan-to'g'ri beriladigan argumentlar. Quruvchi ularniKeyboardButtonga o'tkazadi.- Telefon yuborilganda matn emas,
message.contactkeladi. Shuning uchun uni@router.message(F.contact)bilan tutamiz,F.text == "..."bilan emas. - Joylashuv yuborilganda
message.locationkeladi (latitude,longitude). request_contact/request_locationfaqat private chatda (shaxsiy yozishmada) ishlaydi. Guruhda emas.
OFFLINE tekshirildi:
builder.button(text="...", request_contact=True)natijasidaas_markup()ichidagi tugmaningrequest_contactmaydoniTruebo'ldi; xuddi shundayrequest_location=True->request_location=True. Ya'ni quruvchi bu bayroqlarni to'g'ri o'tkazadi.Jonli botda foydalanuvchi "Telefon raqamni yuborish" ni bosadi, Telegram "Raqamingizni ulashasizmi?" deb so'raydi, rozilik bersa bot raqamni oladi (illustrativ β token + internet kerak; soxta "raqam keldi" deyilmaydi).
6.5. Inline klaviatura: InlineKeyboardBuilder¶
Inline klaviatura xabarning ostiga yopishadi va bosilganda matn emas, callback_query yuboradi. To'g'ri import:
callback_data va url tugmalari¶
from aiogram import Router
from aiogram.filters import Command
from aiogram.types import Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
router = Router()
@router.message(Command("ovoz"))
async def poll(message: Message) -> None:
builder = InlineKeyboardBuilder()
builder.button(text="Ha", callback_data="vote:yes")
builder.button(text="Yo'q", callback_data="vote:no")
builder.button(text="Saytga o'tish", url="https://ioqil.uz")
# 1-qator: Ha + Yo'q (2 ta), 2-qator: URL tugma (1 ta)
builder.adjust(2, 1)
await message.answer(
"Loyihani yoqtirdingizmi?",
reply_markup=builder.as_markup(),
)
Ikki xil tugma:
- callback tugma β
callback_data="..."beriladi. Bosilganda foydalanuvchiga matn yuborilmaydi; botgacallback_querykeladi va uning.datamaydonida aynan shu satr bo'ladi ("vote:yes"). Bu botda biror amal bajarish uchun. - URL tugma β
url="https://..."beriladi. Bosilganda Telegram brauzerni ochadi yoki ilovaga o'tadi. Botga hech narsa kelmaydi.
Diqqat:
callback_dataβ 64 baytdan oshmasligi kerak (Telegram cheklovi). Shuning uchun u qisqa bo'lishi shart. Murakkab ma'lumotni (masalan,vote:yes:user42:item9) tartibli saqlash uchunCallbackDatafactory ishlatiladi β buni quyida ko'ramiz, chuqurroq 07-bobda.
Inline tugma bosilganda: callback_query¶
Inline tugma bosilganda botga CallbackQuery keladi. Uni @router.callback_query(...) bilan tutamiz:
from aiogram import Router, F
from aiogram.types import CallbackQuery
router = Router()
@router.callback_query(F.data == "vote:yes")
async def vote_yes(callback: CallbackQuery) -> None:
# 1) Foydalanuvchiga qisqa bildirishnoma (yuqorida chiqadi)
await callback.answer("Ovozingiz uchun rahmat!")
# 2) Asl xabar matnini o'zgartirish (ixtiyoriy)
await callback.message.edit_text("Siz 'Ha' deb ovoz berdingiz.")
@router.callback_query(F.data == "vote:no")
async def vote_no(callback: CallbackQuery) -> None:
await callback.answer("Tushunarli.")
await callback.message.edit_text("Siz 'Yo'q' deb ovoz berdingiz.")
Eng muhim qoida: har bir callback_query ga callback.answer() chaqiring. Aks holda foydalanuvchi tugma bosganda Telegram'da "soatcha" (yuklanish belgisi) bir necha soniya aylanaveradi. callback.answer() bo'sh ham chaqirilsa, soatcha darrov yo'qoladi. Matn bersangiz β yuqorida kichik bildirishnoma chiqadi; show_alert=True bersangiz β ekran o'rtasida ogohlantirish oynasi chiqadi.
Inline tugmaning butun mexanikasi (sahifalash, tahrirlash,
CallbackDatafactory) keyingi 07-bobda chuqur ochiladi. Hozir asosiy oqimni bilsangiz yetarli.
F.data.startswith(...) bilan guruhlash¶
Har bir callback_data uchun alohida handler yozish shart emas. Umumiy prefiks bo'yicha bittada tutib, ichida ajratish mumkin:
@router.callback_query(F.data.startswith("vote:"))
async def handle_vote(callback: CallbackQuery) -> None:
choice = callback.data.split(":")[1] # "yes" yoki "no"
await callback.answer(f"Tanlovingiz: {choice}")
6.6. Klaviaturani o'chirish: ReplyKeyboardRemove¶
Reply klaviatura bir marta yuborilgach, o'chirmaguningizcha turaveradi β hatto botni qayta ishga tushirsangiz ham. Uni olib tashlash uchun ReplyKeyboardRemove() ni reply_markup= ga berasiz:
from aiogram import Router, F
from aiogram.types import Message, ReplyKeyboardRemove
router = Router()
@router.message(F.text == "Chiqish")
async def logout(message: Message) -> None:
await message.answer(
"Klaviatura yopildi. Rahmat!",
reply_markup=ReplyKeyboardRemove(),
)
Muhim: klaviatura o'chishi uchun xabar bilan birga ReplyKeyboardRemove() yuborilishi kerak β ya'ni o'chirish ham xabar yuborish orqali bo'ladi. Bo'sh klaviatura jo'natib bo'lmaydi, shuning uchun har doim qisqa matn (masalan "Tayyor") bilan birga yuboring.
one_time_keyboard=True bilan adashtirmang: u klaviaturani faqat yig'adi (foydalanuvchi qaytadan ocha oladi), ReplyKeyboardRemove esa butunlay olib tashlaydi.
OFFLINE tekshirildi:
ReplyKeyboardRemove()obyektiningremove_keyboardmaydoniTrueekanligi tasdiqlandi β Telegram aynan shu bayroqdan klaviaturani olib tashlash kerakligini tushunadi.
6.7. Majburiy javob: ForceReply¶
ForceReply β bu klaviatura emas, balki Telegram'ga "foydalanuvchining keyingi xabarini avtomatik shu xabarga reply qilib qo'y" deb aytadigan obyekt. Foydalanuvchi xabar yozishni boshlasa, kirish maydoni avtomatik "reply" rejimiga o'tadi. Bu bitta savolga aniq javob kutilganda foydali (masalan, "Ismingizni kiriting").
from aiogram import Router
from aiogram.filters import Command
from aiogram.types import Message, ForceReply
router = Router()
@router.message(Command("ism"))
async def ask_name(message: Message) -> None:
await message.answer(
"Ismingizni kiriting:",
reply_markup=ForceReply(input_field_placeholder="Ismingiz..."),
)
input_field_placeholder="..."β kirish maydonida ko'rinadigan ko'rsatma.selective=Trueβ guruhda: faqat reply qilingan foydalanuvchiga majburiy reply qo'yiladi.
Halol eslatma: zamonaviy botlarda bir savol-bir javob senariysi ko'pincha FSM (holatlar mashinasi, 08-bob) bilan boshqariladi β u ancha kuchliroq.
ForceReplyesa eng oddiy, holatsiz holatlar uchun yengil yechim. Ikkalasini ham bilib qo'ying.OFFLINE tekshirildi:
ForceReply(input_field_placeholder="Ismingiz?", selective=True)->force_reply=True,input_field_placeholder="Ismingiz?",selective=True.
6.8. To'liq misol: menyu + inline (offline tekshirilgan)¶
Endi hamma narsani birlashtiramiz. Quyidagi bot: /start da reply menyu beradi; "Mahsulotlar" bosilsa inline tugmalar chiqaradi; inline tugma bosilsa callback ishlaydi. Bu kodning routing qismi soxta Update lar bilan offline sinaldi (token kerak emas).
import asyncio
import os
from aiogram import Bot, Dispatcher, Router, F
from aiogram.filters import CommandStart
from aiogram.types import Message, CallbackQuery
from aiogram.utils.keyboard import ReplyKeyboardBuilder, InlineKeyboardBuilder
router = Router()
def main_menu():
b = ReplyKeyboardBuilder()
b.button(text="Mahsulotlar")
b.button(text="Aloqa")
b.adjust(2)
return b.as_markup(resize_keyboard=True, input_field_placeholder="Menyudan tanlang...")
@router.message(CommandStart())
async def start(message: Message) -> None:
await message.answer("Salom! Quyidagi menyudan tanlang:", reply_markup=main_menu())
@router.message(F.text == "Mahsulotlar")
async def products(message: Message) -> None:
b = InlineKeyboardBuilder()
b.button(text="Telefonlar", callback_data="cat:phones")
b.button(text="Noutbuklar", callback_data="cat:laptops")
b.adjust(1)
await message.answer("Toifani tanlang:", reply_markup=b.as_markup())
@router.callback_query(F.data.startswith("cat:"))
async def show_category(callback: CallbackQuery) -> None:
cat = callback.data.split(":")[1]
await callback.answer() # soatchani to'xtatish
await callback.message.edit_text(f"'{cat}' toifasidagi mahsulotlar...")
async def main() -> None:
bot = Bot(token=os.environ["BOT_TOKEN"])
dp = Dispatcher()
dp.include_router(router)
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())
Bu botni jonli ishga tushirish uchun BOT_TOKEN ni .env orqali bering (02-bobdagidek) va python bot.py qiling β bu jonli Telegram talab qiladi (illustrativ). Lekin routing to'g'riligini biz tokensiz, quyidagicha tekshirdik.
Diqqat β nega handlerlar
answer()chaqirmaydi? Yuqoridagibot.pydagi handlerlarmessage.answer()/callback.answer()/edit_text()chaqiradi. Agar shu handlerlarni soxta token bilanfeed_updateorqali ishga tushirsangiz, har biranswer()api.telegram.orgga REAL HTTP so'rov yuboradi va401->TelegramUnauthorizedErrorko'taradi (soxta token bilan crash bo'ladi). Demak, routingni tokensiz tekshirish uchun handler bot API ni chaqirmasligi kerak. Quyida handlerlar API o'rnigalogro'yxatiga yozadi β bu qaysi handler ishga tushganini (ya'ni yo'naltirish to'g'riligini) HTTP'siz isbotlaydi:
# test_routing.py (pytest kerak emas β oddiy asyncio.run bilan)
import asyncio
from datetime import datetime
from aiogram import Bot, Dispatcher, Router, F
from aiogram.filters import CommandStart
from aiogram.types import Update, Message, CallbackQuery, Chat, User
# Routing tekshiruvida handlerlar bot API (answer/edit_text) ni CHAQIRMAYDI β
# faqat `log` ro'yxatiga yozadi. Shu sabab HTTP yuborilmaydi, token kerak emas.
router = Router()
log: list[str] = []
@router.message(CommandStart())
async def start(message: Message) -> None:
log.append("start")
@router.message(F.text == "Mahsulotlar")
async def products(message: Message) -> None:
log.append("products")
@router.callback_query(F.data.startswith("cat:"))
async def show_category(callback: CallbackQuery) -> None:
log.append("cb:" + callback.data.split(":")[1])
def make_message(text: str) -> Message:
return Message(
message_id=1, date=datetime.now(),
chat=Chat(id=1, type="private"),
from_user=User(id=1, is_bot=False, first_name="Test"),
text=text,
)
async def main():
bot = Bot(token="123456:AAH-FakeTest_abc") # SOXTA token (handlerlar API
dp = Dispatcher() # chaqirmagani uchun HTTP ketmaydi)
dp.include_router(router)
await dp.feed_update(bot, Update(update_id=1, message=make_message("/start")))
await dp.feed_update(bot, Update(update_id=2, message=make_message("Mahsulotlar")))
cb = CallbackQuery(
id="cb1", from_user=User(id=1, is_bot=False, first_name="Test"),
chat_instance="ci1", message=make_message("Toifani tanlang:"),
data="cat:phones",
)
await dp.feed_update(bot, Update(update_id=3, callback_query=cb))
await bot.session.close()
print(log) # -> ['start', 'products', 'cb:phones']
asyncio.run(main())
OFFLINE tekshirildi: aynan yuqoridagi snippet (handlerlar
logro'yxatiga yozadigan variant) ishga tushirildi va['start', 'products', 'cb:phones']chiqardi β ya'ni/start, "Mahsulotlar" vacat:phonescallbacki to'g'ri handlerlarga yo'naltirildi. Real Telegram'ga so'rov ketmadi (handlerlar API chaqirmaydi + soxta token +bot.session.close()); bu faqat routing mantiqini isbotlaydi. Jonlibot.pydagianswer()/edit_text()chaqiruvlari esa BotFather token + internet talab qiladi (illustrativ).
6.9. Inline vs reply: qachon qaysi?¶
Bu bobning eng amaliy savoli. Mana aniq qo'llanma:
Reply klaviatura ishlat, agar:
- doimiy asosiy menyu kerak bo'lsa (har doim ko'rinib turadigan "Katalog / Profil / Yordam");
- telefon raqami yoki joylashuv so'ramoqchi bo'lsangiz (
request_contact/request_locationfaqat reply'da); - foydalanuvchining tanlovi mohiyatan "matn yuborish"ga teng bo'lsa (u "Katalog" yozgandek bo'ladi).
Inline klaviatura ishlat, agar:
- tugmalar bitta xabarga bog'liq bo'lsa (ovoz berish, "ha/yo'q", bitta mahsulot uchun "savatga qo'shish");
- sahifalash (pagination β "oldingi / keyingi") kerak bo'lsa;
- tugma bosilgach xabarni tahrirlamoqchi bo'lsangiz (
edit_text/edit_reply_markup) β masalan, sozlamani yoqib-o'chirish; - URL ochish yoki to'lov tugmasi kerak bo'lsa (
url,pay); - chatni "matn xabar"lar bilan ifloslantirmaslik muhim bo'lsa (inline bosishda foydalanuvchi xabar yubormaydi).
Amalda ko'p botlar ikkalasini ham ishlatadi: pastda doimiy reply-menyu, ichidagi har bir bo'limda esa inline tugmalar. Reply β "navigatsiya paneli", inline β "amal tugmalari" deb o'ylang.
Telegram'da bitta xabar bilan bir vaqtning o'zida ham reply, ham inline yubora olmaysiz β
reply_markupbitta bo'ladi. Ammo bir xabarda inline, keyingi xabarda reply yuborib, ketma-ket boshqarish mumkin.
6.10. Tez-tez uchraydigan xatolar¶
| Xato | Sabab | To'g'ri yechim |
|---|---|---|
builder.button("Katalog") ishlamayapti |
text nomli argument |
builder.button(text="Katalog") |
| Klaviatura ulkan ko'rinmoqda | resize_keyboard berilmagan |
as_markup(resize_keyboard=True) |
| Inline tugma bosilganda "soatcha" aylanaveradi | callback.answer() chaqirilmagan |
Har handler oxirida await callback.answer() |
request_contact ishlamayapti |
guruhda sinalgan | Faqat private chatda ishlaydi |
| Reply klaviatura ketmayapti | ReplyKeyboardRemove yuborilmagan |
reply_markup=ReplyKeyboardRemove() (matn bilan) |
callback_data xato |
64 baytdan oshgan yoki F.data mos kelmaydi |
Qisqa data; F.data == "..." yoki .startswith(...) |
# β ESKI 2.x β ISHLATMANG
# from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
# kb = ReplyKeyboardMarkup(resize_keyboard=True, row_width=2)
# kb.add(KeyboardButton("Katalog"), KeyboardButton("Savatcha")) # 2.x add() uslubi
# β
3.x β TO'G'RI
from aiogram.utils.keyboard import ReplyKeyboardBuilder
builder = ReplyKeyboardBuilder()
builder.button(text="Katalog")
builder.button(text="Savatcha")
builder.adjust(2)
markup = builder.as_markup(resize_keyboard=True)
Mashqlar¶
Oson¶
ReplyKeyboardBuilderbilan 3 ta tugmadan iborat (Profil,Buyurtmalar,Yordam) menyu yasang. Hammasi bitta qatorda bo'lsin (adjustishlatmang yoki to'g'risini tanlang).- Oldingi menyuni
adjust(2)bilan qaytadan joylang. Natijada qatorlar qanday bo'ladi? Kodda izoh sifatida yozing. as_markup()garesize_keyboard=Truevainput_field_placeholder="Tanlang..."bering. Yasalgan obyektningresize_keyboardvainput_field_placeholdermaydonlariniprintqilib tekshiring (offline).InlineKeyboardBuilderbilan ikkita callback tugma yasang:Ha(callback_data="ans:yes") vaYo'q(callback_data="ans:no"). Ikkisi bitta qatorda bo'lsin.- Bitta URL tugma yasang: matni "GitHub",
url="https://github.com". Yasalgan markupdagi tugmaningurlmaydoniniprintqiling. ReplyKeyboardRemove()obyektini yarating va uningremove_keyboardmaydonini chop eting.
O'rta¶
- Reply klaviatura yasang:
Telefon yuborish(request_contact=True) vaJoylashuv yuborish(request_location=True), har biri alohida qatorda. Yasalgan markupdagi mos tugmalarningrequest_contact/request_locationmaydonlarini tekshiring (offline). - 6 ta raqamli inline tugma (
1..6,callback_data="n:1"..."n:6") yasang vaadjust(3)bilan joylang. Qatorlar nechta tugmadan bo'ladi? - Oldingi 6 ta tugmani
adjust(2, 3)bilan joylang. Naqsh qanday tarqaladi β izohda tushuntiring. @router.callback_query(F.data.startswith("n:"))handler yozing: u callback ichidan raqamni ajratib (split(":")[1]),callback.answer(f"Siz {raqam} ni tanladingiz")qilsin.ForceReply(input_field_placeholder="Email...")ni yuboradigan/emailhandler yozing. Obyektningforce_replyvainput_field_placeholdermaydonlarini tekshiring (offline).F.contacthandler yozing: umessage.contact.phone_numberni javob qilib qaytarsin. (Routing'ni soxtaUpdatebilan tekshirib ko'rishni urinib ko'ring βContactobyektini yasabMessagega joylang.)
Qiyin¶
- To'liq mini-bot yozing:
/start-> reply menyu (Katalog,Aloqa);Katalogbosilsa -> inline (Telefonlar/Noutbuklar,callback_data="cat:..."); inline bosilsa ->callback.answer()vaedit_text. So'ngfeed_updatebilan uchala bosqichni offline tekshiring (token soxta). - "Kalkulyator" inline klaviaturasini yasang: 9 ta raqam tugmasi (
1..9)adjust(3)bilan 3x3 panjara, ostida0va=alohida qatorda. Jami qatorlar tuzilishini chop eting. adjust(1, 2)vaadjust(1, 2, repeat=True)ni 7 ta tugmada solishtiring. Ikkalasi uchun qatorlar uzunliklari ro'yxatini chop eting va farqini izohda yozing.- Bitta funksiya yozing:
build_menu(items: list[str], per_row: int)βitemsmatnlaridan reply klaviatura yasab, har qatordaper_rowtadan joylasin varesize_keyboard=Truebilanas_markupqaytarsin. Uni["A","B","C","D","E"], per_row=2bilan sinab, qatorlarni chop eting.
Yechimlar
1. adjust chaqirilmasa reply klaviaturada hamma tugma bitta qatorga tushadi β demak bu yerda hech narsa kerak emas (yoki aniqlik uchun adjust(3)).
from aiogram.utils.keyboard import ReplyKeyboardBuilder
b = ReplyKeyboardBuilder()
for t in ("Profil", "Buyurtmalar", "Yordam"):
b.button(text=t)
markup = b.as_markup(resize_keyboard=True)
print([[btn.text for btn in row] for row in markup.keyboard])
# -> [['Profil', 'Buyurtmalar', 'Yordam']] (bitta qator)
2. adjust(2) -> 3 ta tugma [2, 1] bo'ladi: 1-qator 2 ta, 2-qator 1 ta.
b = ReplyKeyboardBuilder()
for t in ("Profil", "Buyurtmalar", "Yordam"):
b.button(text=t)
b.adjust(2)
print([len(r) for r in b.as_markup().keyboard]) # -> [2, 1]
3.
b = ReplyKeyboardBuilder()
b.button(text="Profil")
m = b.as_markup(resize_keyboard=True, input_field_placeholder="Tanlang...")
print(m.resize_keyboard) # -> True
print(m.input_field_placeholder) # -> Tanlang...
4.
from aiogram.utils.keyboard import InlineKeyboardBuilder
b = InlineKeyboardBuilder()
b.button(text="Ha", callback_data="ans:yes")
b.button(text="Yo'q", callback_data="ans:no")
b.adjust(2)
m = b.as_markup()
print([[(x.text, x.callback_data) for x in r] for r in m.inline_keyboard])
# -> [[('Ha', 'ans:yes'), ("Yo'q", 'ans:no')]]
5.
b = InlineKeyboardBuilder()
b.button(text="GitHub", url="https://github.com")
m = b.as_markup()
print(m.inline_keyboard[0][0].url) # -> https://github.com
6.
from aiogram.types import ReplyKeyboardRemove
print(ReplyKeyboardRemove().remove_keyboard) # -> True
7.
b = ReplyKeyboardBuilder()
b.button(text="Telefon yuborish", request_contact=True)
b.button(text="Joylashuv yuborish", request_location=True)
b.adjust(1)
m = b.as_markup(resize_keyboard=True)
print(m.keyboard[0][0].request_contact) # -> True
print(m.keyboard[1][0].request_location) # -> True
8. adjust(3) -> oxirgi o'lcham (3) qolganlarga takrorlanadi: 6 ta tugma [3, 3].
b = InlineKeyboardBuilder()
for i in range(1, 7):
b.button(text=str(i), callback_data=f"n:{i}")
b.adjust(3)
print([len(r) for r in b.as_markup().inline_keyboard]) # -> [3, 3]
9. adjust(2, 3) -> 1-qator 2 ta, keyin oxirgi o'lcham (3) takrorlanadi: 6 ta tugma [2, 3, 1] (2 + 3 = 5, qolgan 1 ta oxirgi qatorda). Naqsh emas, oxirgi son takrorlanadi.
b = InlineKeyboardBuilder()
for i in range(1, 7):
b.button(text=str(i), callback_data=f"n:{i}")
b.adjust(2, 3)
print([len(r) for r in b.as_markup().inline_keyboard]) # -> [2, 3, 1]
10.
from aiogram import Router, F
from aiogram.types import CallbackQuery
router = Router()
@router.callback_query(F.data.startswith("n:"))
async def pick_number(callback: CallbackQuery) -> None:
n = callback.data.split(":")[1]
await callback.answer(f"Siz {n} ni tanladingiz")
11.
from aiogram.types import ForceReply
fr = ForceReply(input_field_placeholder="Email...")
print(fr.force_reply) # -> True
print(fr.input_field_placeholder) # -> Email...
12.
from aiogram import Router, F
from aiogram.types import Message
router = Router()
@router.message(F.contact)
async def got_contact(message: Message) -> None:
await message.answer(message.contact.phone_number)
Routing tekshiruvi (offline, soxta Update):
import asyncio
from datetime import datetime
from aiogram import Bot, Dispatcher
from aiogram.types import Update, Message, Chat, User, Contact
async def main():
bot = Bot(token="123456:AAH-FakeTest_abc")
dp = Dispatcher()
dp.include_router(router)
msg = Message(
message_id=1, date=datetime.now(),
chat=Chat(id=1, type="private"),
from_user=User(id=1, is_bot=False, first_name="T"),
contact=Contact(phone_number="+998901234567", first_name="T", user_id=1),
)
# handler bot API chaqirmasligi uchun answer'ni mock qilish kerak;
# bu yerda faqat handler tanlanishini ko'rsatish maqsadi (illustrativ).
await bot.session.close()
asyncio.run(main())
13.
import asyncio
from datetime import datetime
from aiogram import Bot, Dispatcher, Router, F
from aiogram.filters import CommandStart
from aiogram.types import (
Update, Message, CallbackQuery, Chat, User,
)
from aiogram.utils.keyboard import ReplyKeyboardBuilder, InlineKeyboardBuilder
router = Router()
log = []
@router.message(CommandStart())
async def start(m: Message):
b = ReplyKeyboardBuilder()
b.button(text="Katalog"); b.button(text="Aloqa"); b.adjust(2)
log.append("start")
@router.message(F.text == "Katalog")
async def catalog(m: Message):
b = InlineKeyboardBuilder()
b.button(text="Telefonlar", callback_data="cat:phones")
b.button(text="Noutbuklar", callback_data="cat:laptops")
b.adjust(1)
log.append("catalog")
@router.callback_query(F.data.startswith("cat:"))
async def cat(c: CallbackQuery):
log.append("cb:" + c.data.split(":")[1])
def msg(text):
return Message(message_id=1, date=datetime.now(),
chat=Chat(id=1, type="private"),
from_user=User(id=1, is_bot=False, first_name="T"), text=text)
async def main():
bot = Bot(token="123456:AAH-FakeTest_abc")
dp = Dispatcher()
dp.include_router(router)
await dp.feed_update(bot, Update(update_id=1, message=msg("/start")))
await dp.feed_update(bot, Update(update_id=2, message=msg("Katalog")))
cb = CallbackQuery(id="1", from_user=User(id=1, is_bot=False, first_name="T"),
chat_instance="ci", message=msg("Toifa:"), data="cat:phones")
await dp.feed_update(bot, Update(update_id=3, callback_query=cb))
await bot.session.close()
print(log) # -> ['start', 'catalog', 'cb:phones']
asyncio.run(main())
14.
from aiogram.utils.keyboard import InlineKeyboardBuilder
b = InlineKeyboardBuilder()
for i in range(1, 10):
b.button(text=str(i), callback_data=f"c:{i}")
b.adjust(3) # 9 ta raqam -> 3 qatorda 3 tadan
# pastki qator: 0 va = ni alohida qo'shamiz
extra = InlineKeyboardBuilder()
extra.button(text="0", callback_data="c:0")
extra.button(text="=", callback_data="c:eq")
b.attach(extra) # ikkinchi quruvchini biriktiramiz
print([len(r) for r in b.as_markup().inline_keyboard])
# -> [3, 3, 3, 2] (3x3 panjara + oxirgi qatorda 0 va =)
Izoh:
attach()ikkinchi quruvchini birinchisiga ulaydi va uning o'zadjustini saqlaydi. Soddaroq variant: hamma tugmani bitta quruvchiga qo'shibadjust(3, 3, 3, 2)berish.
15.
from aiogram.utils.keyboard import InlineKeyboardBuilder
def rows(repeat):
b = InlineKeyboardBuilder()
for i in range(7):
b.button(text=str(i), callback_data=f"x:{i}")
b.adjust(1, 2, repeat=repeat)
return [len(r) for r in b.as_markup().inline_keyboard]
print("no repeat:", rows(False)) # -> [1, 2, 2, 2] (oxirgi 2 takrorlanadi)
print("repeat: ", rows(True)) # -> [1, 2, 1, 2, 1] (naqsh 1,2 takrorlanadi)
# Farq: repeat=False -> faqat OXIRGI son takrorlanadi;
# repeat=True -> butun NAQSH (1,2) qayta-qayta qo'llanadi.
16.
from aiogram.utils.keyboard import ReplyKeyboardBuilder
def build_menu(items: list[str], per_row: int):
b = ReplyKeyboardBuilder()
for it in items:
b.button(text=it)
b.adjust(per_row)
return b.as_markup(resize_keyboard=True)
m = build_menu(["A", "B", "C", "D", "E"], per_row=2)
print([[btn.text for btn in r] for r in m.keyboard])
# -> [['A', 'B'], ['C', 'D'], ['E']]
β¬ οΈ Oldingi: 05 β Xabar yuborish, formatlash va media Β· π README Β· Keyingi: 07 β Callback query va inline rejim β‘οΈ