02 β Birinchi bot: echo va /start¶
β¬ οΈ Oldingi: 01 β Telegram botlar va aiogram bilan tanishuv Β· π README Β· Keyingi: 03 β Handlerlar va Router β‘οΈ
Bu bobda: birinchi ishlaydigan botimizni noldan quramiz. BotFather'dan token olamiz va uni
.envfaylda (kodga emas) saqlaymiz. Uchta asosiy qism βBot,Dispatcher,Routerβ nima ekanini va qanday bog'lanishini tushunamiz./startbuyrug'iga javob beradigan handler va har qanday matnni qaytaradigan echo handler yozamiz. Nega deyarli hamma narsaasync/awaitekanini,asyncio.run(main())vadp.start_polling(bot)nima qilishini ko'ramiz. Yo'l-yo'lakayUpdateqanday qilib Telegram serveridan sizning funksiyangizgacha "sayohat qilishi"ni kuzatamiz.Halollik eslatmasi: bu bobdagi handler mantig'i (
/startva echo) haqiqatan ham OFFLINE β Telegram tokeni va internetsiz βdispatcher.feed_update(...)orqali soxtaUpdateuzatib tekshirilgan. Filtrlar (CommandStart,Command,F.text) va buyruq parslash ham shunday tekshirilgan. Lekin botni jonli ishga tushirish (start_polling, real xabar yuborish/qabul qilish,getMe) BotFather tokeni + internet talab qiladi. Shu sababli "botni terminalda ishga tushiring" bo'limlari illustrativ deb belgilangan: kod to'g'ri, ammo natijani siz o'z tokeningiz bilan ko'rasiz. Hech qayerda soxta "xabar yetib bordi" degan natija yozilmagan.
Yo'l xaritasi: nima quramiz?¶
Birinchi botimiz juda kichik bo'ladi, lekin u to'liq ishlaydigan bot β kichik o'yinchoq emas:
- Foydalanuvchi
/startyozsa, bot uni ism bilan salomlaydi. - Foydalanuvchi boshqa har qanday matn yozsa, bot o'sha matnni qaytaradi (echo, ya'ni "aks-sado").
Bor-yo'g'i shu. Lekin shu kichik dasturda Telegram botlarining BARCHA asosiy g'oyalari mujassam: token, Bot, Dispatcher, Router, handler, filtr, async/await va polling. Keyingi boblarda biz shu skeletga qo'shimcha "go'sht" qo'shamiz, lekin suyak shu yerda quriladi.
Bu bob Python'ni bilasiz deb faraz qiladi (async/await, dekorator, funksiya, import). Agar async def va await sizga begona bo'lsa, avval Python qo'llanmasidagi asinxron dasturlash bo'limini ko'rib chiqing. Bu yerda biz Python'ni emas, aiogram'ga xos har bir narsani tushuntiramiz.
Token: BotFather'dan olib, .env faylda saqlash¶
Botingiz Telegram bilan gaplashishi uchun unga token kerak. Token β bu botning paroli kabi maxfiy satr. U @BotFather botidan olinadi.
Token olish (jonli β Telegram talab qiladi)¶
Illustrativ: bu qadamlar real Telegram ilovasi va internet talab qiladi. Token sizniki, shaxsiy β uni hech kimga bermang.
- Telegram'da
@BotFatherni oching. /newbotbuyrug'ini yuboring.- Botga nom bering (masalan:
Mening Echo Botim). - Botga username bering β u
botbilan tugashi shart (masalan:mening_echo_bot). - BotFather sizga token beradi. U taxminan shunday ko'rinadi:
Bu satr β sizning tokeningiz. Ikki qismdan iborat: ikki nuqtagacha bo'lgan raqam (bot ID si) va undan keyingi maxfiy qism.
Diqqat: token β parol kabi. Uni GitHub'ga, skrinshotga, hech qaerga oshkor qilmang. Agar oshkor bo'lib qolsa, BotFather'da
/revokebilan eski tokenni bekor qiling va yangisini oling.
Nega token kodga yozilmaydi?¶
Yangi boshlovchilar ko'pincha tokenni to'g'ridan kodga yozadi:
# β Yomon: token kodning ichida ochiq turibdi
bot = Bot(token="8012345678:AAH-dEfGhIjKlMnOpQrStUvWxYz1234567890")
Bu xavfli. Agar siz kodni GitHub'ga git commit qilsangiz, token butun dunyoga oshkor bo'ladi. Bir necha daqiqada robotlar uni topib, botingizni egallab olishi mumkin.
To'g'ri yo'l β tokenni .env faylda saqlash va uni .gitignorega qo'shish. Quyidagi rasm farqni ko'rsatadi:
.env faylini yaratish¶
Loyiha papkasida .env nomli fayl yarating:
.envβ bu oddiy matn fayli, ichidaKALIT=qiymatko'rinishidagi qatorlar bo'ladi. Bu yerda haqiqiy tokenni yozasiz (yuqoridagi soxta tokenni emas).
Endi .gitignore fayliga .envni qo'shing, shunda u hech qachon gitga tushmaydi:
.env'ni o'qish: python-dotenv¶
.env faylini Python'ga o'qitish uchun python-dotenv kutubxonasi ishlatiladi:
Kodda esa shunday o'qiymiz:
import os
from dotenv import load_dotenv
load_dotenv() # .env faylini o'qib, muhit o'zgaruvchilariga yuklaydi
BOT_TOKEN = os.getenv("BOT_TOKEN") # endi tokenni shu yerdan olamiz
load_dotenv() .env ichidagi BOT_TOKEN=... qatorini o'qib, uni operatsion tizimning muhit o'zgaruvchisi (environment variable) sifatida yuklaydi. So'ng os.getenv("BOT_TOKEN") o'sha qiymatni qaytaradi. Token endi kodda emas, faylda.
Eslatma:
.envfaylsiz ham token berish mumkin β terminalda muhit o'zgaruvchisini to'g'ridan o'rnatish orqali. Lekin.envqulayroq, shuning uchun biz shu usulni ishlatamiz.
Uchta asosiy qism: Bot, Dispatcher, Router¶
aiogram 3.x da bot uchta asosiy obyekt ustiga quriladi. Ularni tushunsangiz, qolgani oson ketadi.
Bot β bu Telegram bilan aloqa liniyasi. U tokenni saqlaydi va send_message, answer_photo, getMe kabi Telegram metodlarini chaqiradi. Bot β bu "telefon": xabar yuborish va olish uchun.
Dispatcher β bu boshqaruvchi miya. U Telegram'dan kelgan Updatelarni qabul qiladi va ularni qaysi funksiya qayta ishlashini hal qiladi. dp.start_polling(bot) ham Dispatcher'da.
Router β bu handlerlar uyi. Siz handler funksiyalaringizni @router.message(...) dekoratori bilan Router'ga "ro'yxatdan o'tkazasiz". Router filtrlarni saqlaydi va kelgan xabar qaysi handlerga mos kelishini tekshiradi. So'ng Router'ni Dispatcher'ga ulaysiz:
from aiogram import Router
router = Router()
# ... handlerlar shu yerda ...
dp.include_router(router) # router'ni dispatcher'ga ulash
Tarixiy eslatma (muhim!): aiogram 2.x da
Routeryo'q edi va handlerlar to'g'ridan@dp.message_handler(...)bilan yozilardi. aiogram 3.x da bu o'zgardi: handlerlarRouterga yoziladi va Router Dispatcher'ga ulanadi. Internetdagi eski misollarda@dp.message_handleryokiexecutor.start_pollingko'rsangiz β bu 2.x, bizga yaramaydi. Biz faqat 3.x idiomini ishlatamiz.
Soddalashtirilgan variant: Dispatcher'ga to'g'ridan yozish¶
Kichik botlar uchun alohida Router yaratmasdan, handlerlarni to'g'ridan Dispatcherga ham yozish mumkin (Dispatcherning o'zi ham Router'ning bir turi):
dp = Dispatcher()
@dp.message(CommandStart()) # router o'rniga to'g'ridan dp
async def start(message): ...
Bu rasmiy aiogram quickstart'da ham shunday. Lekin loyiha kattalashganda handlerlarni Routerlarga bo'lib, modullarga ajratish qulayroq (buni 03-bobda ko'ramiz). Shu sababli biz odat tariqasida boshidanoq Router ishlatamiz β bu yaxshi amaliyot.
async/await: nega hammasi "async"?¶
Telegram boti bir vaqtning o'zida yuzlab, minglab foydalanuvchi bilan gaplashadi. Agar bot bir xabarni qayta ishlayotganda "qotib qolsa" (masalan, Telegram serveridan javob kutib turib), qolgan hamma kutib qolardi.
async/await shu muammoni hal qiladi. Telegram'ga so'rov yuborib, javob kutayotgan paytda bot boshqa foydalanuvchilarning xabarlarini qayta ishlay oladi. Shuning uchun aiogram'da:
- Har bir handler
async defbilan yoziladi. - Telegram'ga so'rov yuboradigan har bir chaqiruv
awaitbilan kutiladi:await message.answer(...). - Hamma narsa
asyncio.run(main())ichida ishga tushadi.
async def main():
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
dp.include_router(router)
await dp.start_polling(bot) # await β chunki polling cheksiz "kutadi"
asyncio.run(main()) # asyncio'ning "asosiy" ishga tushirish nuqtasi
Nega
asyncio.run(main())?async deffunksiyani oddiygina chaqirsangiz, u ishlamaydi β faqat "coroutine" obyekti qaytaradi. Uni haqiqatan ishga tushirish uchun asinxron muhit (event loop) kerak.asyncio.run(main())shu muhitni yaratadi,main()ni ishlatadi va tugaganda muhitni yopadi. Bu β sizning dasturingizning kirish nuqtasi.
await dp.start_polling(bot)nega "tugamaydi"? Polling β bu cheksiz tsikl: bot doimo Telegram'dan "yangi xabar bormi?" deb so'rab turadi. Shuning uchunstart_pollingsizCtrl+Cbosgunga qadar ishlab turadi. Bu normal holat β bot "tinglab turibdi" degani.
Update qanday "sayohat qiladi"?¶
Foydalanuvchi xabar yozganidan sizning funksiyangiz ishlaguncha bo'lgan yo'lni tushunish muhim. Quyidagi rasm shu oqimni ko'rsatadi:
Bosqichma-bosqich:
- Foydalanuvchi botga
/startyozadi. - Xabar Telegram serveriga boradi. Server uni
Updateko'rinishida saqlaydi (Updateβ bu "nimadir yangilik bo'ldi" degan paket). - Sizning bot dasturingiz
getUpdatesso'rovi bilan "yangi update bormi?" deb so'rab turadi. Bu polling deyiladi. - Update kelganda Dispatcher uni qabul qiladi.
- Dispatcher Router'dan o'tkazadi. Router har bir handlerning filtrini tekshiradi.
- Birinchi mos filtr topilganda, o'sha handler ishga tushadi va
message.answer(...)bilan javob qaytaradi.
/start xabari CommandStart() filtriga mos keladi, shuning uchun start_handler ishlaydi. Oddiy matn esa unga mos kelmaydi, lekin F.text filtriga mos keladi, shuning uchun echo_handler ishlaydi.
To'liq birinchi bot: bot.py¶
Endi hammasini birlashtiramiz. Mana to'liq, ishlaydigan bot:
import asyncio
import logging
import os
from dotenv import load_dotenv
from aiogram import Bot, Dispatcher, Router, F
from aiogram.filters import CommandStart
from aiogram.types import Message
# 1) .env'dan tokenni o'qiymiz (kodga yozmaymiz!)
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
# 2) Router yaratamiz va handlerlarni unga yozamiz
router = Router()
@router.message(CommandStart())
async def start_handler(message: Message) -> None:
"""Foydalanuvchi /start yozganda ishlaydi."""
await message.answer(
f"Salom, {message.from_user.first_name}! "
f"Men echo botman. Menga biror narsa yozing β qaytaraman."
)
@router.message(F.text)
async def echo_handler(message: Message) -> None:
"""Har qanday matnli xabarni qaytaradi (echo)."""
await message.answer(message.text)
# 3) main: bot va dispatcher'ni quramiz, polling boshlaymiz
async def main() -> None:
if not BOT_TOKEN:
raise RuntimeError(".env faylida BOT_TOKEN topilmadi!")
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
dp.include_router(router)
# eski update'larni o'tkazib yuboramiz, faqat yangilarini olamiz
await bot.delete_webhook(drop_pending_updates=True)
await dp.start_polling(bot)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
asyncio.run(main())
Endi har bir qismni tushuntiramiz.
Importlar¶
from aiogram import Bot, Dispatcher, Router, F
from aiogram.filters import CommandStart
from aiogram.types import Message
Bot,Dispatcher,Routerβ uchta asosiy qism (yuqorida ko'rdik).Fβ bu magic filter.F.text"xabarda matn bormi?" degan filtrni bildiradi.Forqali xabar maydonlari bo'yicha qulay filtrlar yoziladi.CommandStartβ/startbuyrug'ini aniqlaydigan tayyor filtr.Messageβ kelgan xabarning turi (type hint uchun). Handler argumentinimessage: Messagedeb belgilaymiz.
Anti-eskirish: aiogram 2.x da
from aiogram import executorvatypes.ParseModeishlatilardi. 3.x daexecutoryo'q (uning o'rnidadp.start_polling),ParseModeesaaiogram.enumsichida. Eski importlarni ko'rsangiz β 2.x.
CommandStart() handler¶
@router.message(CommandStart())
async def start_handler(message: Message) -> None:
await message.answer(f"Salom, {message.from_user.first_name}! ...")
@router.message(CommandStart())β bu dekorator funksiyani/startbuyrug'iga bog'laydi. Faqat/startkelganda shu handler ishlaydi.message.from_user.first_nameβ xabarni yozgan foydalanuvchining ismi.from_userβUserobyekti.message.answer(...)β o'sha chatga javob yuboradi.answer"qaysi chatga?" degan savolni avtomatik hal qiladi (xabar kelgan chatga javob beradi).awaitshart, chunki bu Telegram'ga so'rov.
echo handler¶
@router.message(F.text)
async def echo_handler(message: Message) -> None:
await message.answer(message.text)
@router.message(F.text)β faqat matnli xabarlarga mos keladi. Rasm yoki stiker kelsa, bu handler ishlamaydi.message.textβ xabar matni.await message.answer(message.text)β o'sha matnni qaytaradi. Echo tayyor!
Tartib muhim! Router handlerlarni yozilish tartibida tekshiradi.
start_handleryuqorida bo'lgani uchun/startkelganda u ishlaydi (echo_handlerga yetib bormaydi). Agarecho_handlerni yuqoriga qo'ysangiz, u har qanday matnni (/startham matn!) tutib olardi va salomlash ishlamasdi. Buni keyingi bo'limda OFFLINE tekshiramiz.
message.answer va bot.send_message farqi¶
Ikki xil javob berish usuli bor:
# 1) message.answer β xabar kelgan chatga avtomatik javob beradi (qulay)
await message.answer("Salom!")
# 2) bot.send_message β chat_id ni o'zingiz ko'rsatasiz (moslashuvchan)
await bot.send_message(chat_id=message.chat.id, text="Salom!")
message.answer(...) β qisqa yo'l: u chat_idni xabardan o'zi oladi. bot.send_message(...) β to'liq yo'l: boshqa chatga yuborish kerak bo'lsa (masalan adminga xabar berish) shuni ishlatasiz. Echo botda answer qulayroq.
Botni ishga tushirish (illustrativ β token + internet kerak)¶
Halollik: quyidagi blok jonli Telegram talab qiladi. Men buni soxta ravishda "ishladi" deb ko'rsata olmayman β botni siz o'z tokeningiz bilan ishga tushirasiz. Lekin kod to'g'ri (handler mantig'i pastda OFFLINE tekshirilgan).
Loyiha papkangizda:
Terminalda taxminan shunday log chiqadi (illustrativ):
INFO:aiogram.dispatcher:Start polling
INFO:aiogram.dispatcher:Run polling for bot @mening_echo_bot id=8012345678 - 'Mening Echo Botim'
Endi Telegram'da botingizni oching, /start yozing β u sizni ism bilan salomlaydi. Boshqa biror matn yozing β u qaytaradi. Botni to'xtatish uchun terminalda Ctrl+C bosing.
Agar aiogram.utils.token.TokenValidationError xatosini ko'rsangiz β .env dagi token noto'g'ri yoki .env o'qilmagan. Agar Unauthorized ko'rsangiz β token noto'g'ri (BotFather'dan tekshiring).
OFFLINE tekshirish: handler mantig'ini tokensiz sinash¶
Bu kitobning kuchi shunda: biz handler mantig'ini token va internetsiz ham tekshira olamiz. Sirimiz β Dispatcher'ga soxta Update uzatish (feed_update) va bot tarmoq chaqiruvini "mock" qilish.
G'oya:
message.answer(...)ichida bot Telegram'ga so'rov yuborishga uringanda, biz uni soxta (mock) obyekt bilan to'samiz. Shunda haqiqiy tarmoq chaqiruvi bo'lmaydi, lekin handler mantig'i to'liq ishlaydi va biz qaysi matn yuborilganini tekshiramiz.
Mana to'liq tekshiruv (men buni o'z kompyuterimda ishlatib, natijasini ko'rdim):
import asyncio
from datetime import datetime
from unittest.mock import AsyncMock
from aiogram import Bot, Dispatcher, Router, F
from aiogram.filters import CommandStart
from aiogram.methods import SendMessage
from aiogram.types import Message, Chat, User, Update
router = Router()
@router.message(CommandStart())
async def start_handler(message: Message) -> None:
await message.answer(f"Salom, {message.from_user.first_name}!")
@router.message(F.text)
async def echo_handler(message: Message) -> None:
await message.answer(message.text)
def make_update(update_id: int, text: str) -> Update:
"""Soxta Update yasaymiz β xuddi Telegram yuborgandek."""
msg = Message(
message_id=update_id,
date=datetime.now(),
chat=Chat(id=1, type="private"),
from_user=User(id=1, is_bot=False, first_name="Oqil"),
text=text,
)
return Update(update_id=update_id, message=msg)
async def main() -> None:
fake_token = "123456:AAH-FakeTest_abc" # soxta, lekin format to'g'ri
bot = Bot(token=fake_token)
dp = Dispatcher()
dp.include_router(router)
# bot.session β tarmoq qatlami. Uni mock qilamiz: hech narsa yubormaydi.
bot.session = AsyncMock()
sent = []
async def record(b, method, *a, **k): # qaysi metod chaqirildi β yozamiz
sent.append(method)
bot.session.side_effect = record
await dp.feed_update(bot, make_update(1, "/start"))
await dp.feed_update(bot, make_update(2, "Salom dunyo"))
await dp.feed_update(bot, make_update(3, "yana matn"))
texts = [m.text for m in sent if isinstance(m, SendMessage)]
print("Yuborilgan matnlar:", texts)
assert texts == ["Salom, Oqil!", "Salom dunyo", "yana matn"]
print("OK: routing va echo to'g'ri ishladi (offline, tokensiz)")
if __name__ == "__main__":
asyncio.run(main())
Ishga tushirganimda chiqqan natija:
Yuborilgan matnlar: ['Salom, Oqil!', 'Salom dunyo', 'yana matn']
OK: routing va echo to'g'ri ishladi (offline, tokensiz)
Bu nimani isbotlaydi?
/startβstart_handlerishladi va"Salom, Oqil!"yubordi (ismni to'g'ri oldi)."Salom dunyo"va"yana matn"βecho_handlerishladi va matnni aynan qaytardi.- Filtrlar to'g'ri saralayapti:
/startecho'ga tushmadi, oddiy matn salomlash'ga tushmadi.
Hammasi tokensiz tekshirildi. Telegram'ga bironta ham real so'rov ketmadi.
Bu nima uchun ishlaydi? (mexanika)¶
dp.feed_update(bot, update)β Dispatcher'ga "mana sizga update" deb to'g'ridan beradi. Polling kutmaydi β bizning soxta update darhol ishlanadi.bot.session = AsyncMock()βbot.sessionaslida Telegram'ga HTTP so'rov yuboradigan qatlam. UniAsyncMockbilan almashtirsak, hech qanday tarmoq chaqiruvi bo'lmaydi.message.answer(...)ichidabot.session(bot, SendMessage(...))chaqiriladi. Bizside_effectorqali qaysimethodobyekti uzatilganini ushlab, uning.textva.chat_idsini tekshiramiz.
pytest bilan handler testi¶
Yuqoridagini pytestka aylantirsak, doimiy avtomatik testga ega bo'lamiz. pytest-asyncio kerak:
Loyiha papkasida pytest.ini:
test_bot.py:
import pytest
from datetime import datetime
from unittest.mock import AsyncMock
from aiogram import Bot, Dispatcher, Router, F
from aiogram.filters import CommandStart
from aiogram.methods import SendMessage
from aiogram.types import Message, Chat, User, Update
def make_router() -> Router:
router = Router()
@router.message(CommandStart())
async def start_handler(message: Message) -> None:
await message.answer(f"Salom, {message.from_user.first_name}!")
@router.message(F.text)
async def echo_handler(message: Message) -> None:
await message.answer(message.text)
return router
def make_update(uid: int, text: str) -> Update:
msg = Message(
message_id=uid, date=datetime.now(),
chat=Chat(id=42, type="private"),
from_user=User(id=7, is_bot=False, first_name="Oqil"),
text=text,
)
return Update(update_id=uid, message=msg)
@pytest.fixture
def bot():
b = Bot(token="123456:AAH-FakeTest_abc")
b.session = AsyncMock()
return b
@pytest.fixture
def dp():
d = Dispatcher()
d.include_router(make_router())
return d
async def test_start(bot, dp):
await dp.feed_update(bot, make_update(1, "/start"))
method = bot.session.await_args.args[1] # chaqirilgan metod obyekti
assert isinstance(method, SendMessage)
assert method.text == "Salom, Oqil!"
assert method.chat_id == 42
async def test_echo(bot, dp):
await dp.feed_update(bot, make_update(2, "qaytar buni"))
method = bot.session.await_args.args[1]
assert isinstance(method, SendMessage)
assert method.text == "qaytar buni"
Ishga tushirish va natija (men ko'rdim):
Muhim gotcha (men duch keldim): agar
routerni modul darajasida bir marta yaratib, har testdainclude_routerqilsangiz β ikkinchi testdaRuntimeError: Router is already attached to ...xatosi chiqadi. Sababi: bitta Router faqat bitta Dispatcher'ga ulanishi mumkin. Yechim β yuqoridagidek har test uchun yangi Router yaratish (make_router()funksiyasi). Bu 3.x ning haqiqiy xususiyati.
To'liq loyiha tuzilishi¶
Yakuniy echo bot loyihasi shunday ko'rinadi:
mening-bot/
βββ .env # BOT_TOKEN=... (gitignore'da!)
βββ .gitignore # .env, __pycache__/
βββ bot.py # asosiy kod
βββ requirements.txt # aiogram, python-dotenv
requirements.txt:
Bu β har qanday aiogram loyihasining minimal skeleti. Keyingi boblarda biz bot.pyni modullarga (handlers/, keyboards/, ...) bo'lamiz, lekin g'oya o'zgarmaydi.
Cross-link: loyiha papkasini
gitga qo'yib, GitHub'ga deploy qilishni Git/GitHub qo'llanmasida ko'ring..envni.gitignorega qo'shishni unutmang. Agar siz bu botni Node.js'da qanday yozilishi bilan solishtirmoqchi bo'lsangiz β Node.js qo'llanmasidaginode-telegram-bot-apiboblariga qarang.
Tez-tez uchraydigan xatolar¶
| Xato / belgi | Sabab | Yechim |
|---|---|---|
TokenValidationError |
token formati noto'g'ri yoki None |
.env da BOT_TOKEN borligini, load_dotenv() chaqirilganini tekshiring |
Unauthorized (jonli) |
token noto'g'ri/bekor qilingan | BotFather'dan yangi token oling |
/start ham echo bo'lib qaytadi |
echo_handler start_handlerdan yuqorida |
handlerlar tartibini to'g'rilang: maxsus filtr (CommandStart) yuqorida |
RuntimeError: Router is already attached |
bitta Router ikki marta ulangan | har Dispatcher uchun yangi Router yarating |
| Bot javob bermaydi (jonli) | boshqa joyda yana bir nusxa polling qilyapti | faqat bitta bot nusxasini ishga tushiring |
@dp.message_handler ishlamaydi |
bu 2.x sintaksisi | 3.x: @router.message(...) |
Mashqlar¶
Mashqlarning aksariyatini OFFLINE tekshirish mumkin: yuqoridagi
make_update+feed_update+AsyncMocknamunasini ishlating. Token va internet shart emas.
Oson¶
-
Salom matnini o'zgartiring.
start_handlerda botning salomlash matnini o'zingizniki bilan almashtiring (masalan, foydalanuvchi ismidan tashqari, bot nima qila olishini ham yozing). -
/helpqo'shing.Command("help")filtri bilan yangi handler yozing β u botning buyruqlari ro'yxatini qaytarsin. (from aiogram.filters import Command.) -
VERSAL echo. echo handlerni shunday o'zgartiring-ki, u matnni katta harflarda qaytarsin (
message.text.upper()). -
message.answervsbot.send_message. Echo handlernibot.send_message(chat_id=..., text=...)ishlatadigan qilib qayta yozing. Handler argumentlarigabot: Botqo'shing (aiogram uni avtomatik beradi). -
Token tekshiruvi.
main()boshidaBOT_TOKENNonebo'lsa, aniq xato xabari bilan dasturni to'xtatadigan kod yozing.
O'rta¶
-
Handler tartibini buzing.
echo_handlernistart_handlerdan yuqoriga ko'chiring va OFFLINE tekshiruvda/startendi salomlash o'rniga/startmatnini qaytarishini ko'rsating. So'ng nega bunday bo'lganini bir jumlada tushuntiring. -
Hisoblagich bot. Bot har bir matnli xabarga "Bu N-xabaringiz" deb javob bersin (N β shu foydalanuvchidan kelgan xabarlar soni).
/starthisoblagichni nolga tushirsin. (Maslahat: oddiydict/o'zgaruvchida saqlang β hozircha xotirada.) -
/echo matnbuyrug'i.Command("echo")vaCommandObjectishlatib,/echo salomyozilgandasalomqaytariladigan handler yozing. Agar argument bo'lmasa (/echo), foydalanish bo'yicha ko'rsatma bering. -
Faqat matn, boshqasi yo'q. echo'ni
F.textbilan cheklang va matn bo'lmagan har qanday xabarga (rasm, stiker) "Men faqat matn qaytaraman" deb javob beradigan ikkinchi handler qo'shing. -
OFFLINE test yozing. 7-mashqdagi hisoblagich botni
make_update+feed_updatebilan tekshiradigan kod yozing: ketma-ket 3 ta matn yuboring va javoblar "Bu 1-...", "Bu 2-...", "Bu 3-xabaringiz" ekaniniassertqiling.
Qiyin¶
-
Salomlashni boyiting.
/startda inline emas, oddiy javobda foydalanuvchining ismi vaidsini ko'rsating. So'ng OFFLINE testdamake_updatega turlifirst_nameberib, salomlash to'g'ri shakllanishini tekshiring. -
Matn statistikasi. Foydalanuvchi matn yuborsa, bot javobida: matn uzunligi (belgilar soni), so'zlar soni va matnning teskari ko'rinishini qaytarsin. OFFLINE testda bir necha matn bilan to'g'riligini
assertqiling. -
Ikki Router.
commands_router(/start,/help) vaecho_router(matn echo) deb ikkita alohida Router yarating, ikkalasini bitta Dispatcher'gainclude_routerbilan ulang. OFFLINE testda har ikkala router handlerlari to'g'ri ishlashini ko'rsating. (Bu 03-bobning modulyar tuzilmasiga ko'prik.)
Yechimlar
1-mashq yechimi¶
@router.message(CommandStart())
async def start_handler(message: Message) -> None:
await message.answer(
f"Assalomu alaykum, {message.from_user.first_name}!\n\n"
f"Men oddiy echo botman. Menga istalgan matn yozing β "
f"men uni aynan qaytaraman. Yordam uchun /help yozing."
)
Hech qanday yangi tushuncha yo'q β faqat matn o'zgardi. \n qator tashlaydi.
2-mashq yechimi¶
from aiogram.filters import Command
@router.message(Command("help"))
async def help_handler(message: Message) -> None:
await message.answer(
"Mavjud buyruqlar:\n"
"/start β botni boshlash\n"
"/help β yordam\n\n"
"Yoki shunchaki matn yozing β men qaytaraman."
)
Command("help") β /help buyrug'iga mos keladi. Bu handlerni echo_handlerdan yuqorida qo'ying, aks holda /help matn sifatida echo'ga tushib ketadi.
3-mashq yechimi¶
@router.message(F.text)
async def echo_handler(message: Message) -> None:
await message.answer(message.text.upper())
message.text.upper() matnni katta harflarga aylantiradi. "salom" β "SALOM".
4-mashq yechimi¶
from aiogram import Bot
@router.message(F.text)
async def echo_handler(message: Message, bot: Bot) -> None:
await bot.send_message(chat_id=message.chat.id, text=message.text)
aiogram handlerga bot: Bot argumentini avtomatik uzatadi (dependency injection). chat_idni message.chat.iddan olamiz. Natija message.answer bilan bir xil, lekin endi chat'ni biz tanladik.
5-mashq yechimi¶
async def main() -> None:
if not BOT_TOKEN:
raise RuntimeError(
".env faylida BOT_TOKEN topilmadi! "
"Loyiha papkasida .env yaratib, BOT_TOKEN=... yozing."
)
bot = Bot(token=BOT_TOKEN)
...
if not BOT_TOKEN β None ham, bo'sh satr "" ham bu shartga tushadi. Aniq xabar bilan to'xtatamiz β shunda foydalanuvchi muammoni darhol tushunadi.
6-mashq yechimi¶
# echo YUQORIDA (xato tartib):
@router.message(F.text)
async def echo_handler(message: Message) -> None:
await message.answer(message.text)
@router.message(CommandStart())
async def start_handler(message: Message) -> None:
await message.answer("Salom!")
OFFLINE tekshiruv: /start yuborsangiz, javob "/start" bo'ladi ("Salom!" emas), chunki echo_handler birinchi mos kelib, /start matnini qaytaradi.
Tushuntirish: Router handlerlarni yozilish tartibida tekshiradi va birinchi mos kelganida to'xtaydi. /start ham matn (F.text mos keladi), shuning uchun yuqoridagi echo uni tutib oladi va start_handlerga navbat yetib bormaydi. Shu sababli maxsus filtrlar (buyruqlar) doimo umumiy filtrlardan (F.text) yuqorida turishi kerak.
7-mashq yechimi¶
from collections import defaultdict
counts: dict[int, int] = defaultdict(int)
@router.message(CommandStart())
async def start_handler(message: Message) -> None:
counts[message.from_user.id] = 0
await message.answer("Hisoblagich nolga tushdi. Matn yozing.")
@router.message(F.text)
async def echo_handler(message: Message) -> None:
counts[message.from_user.id] += 1
await message.answer(f"Bu {counts[message.from_user.id]}-xabaringiz.")
Har foydalanuvchi uchun alohida hisob yuritamiz (message.from_user.id kalit). /start o'sha foydalanuvchining hisobini nolga tushiradi.
Eslatma: bu hisob xotirada β bot qayta ishga tushsa nolga qaytadi. Doimiy saqlash uchun ma'lumotlar bazasi kerak (SQL qo'llanmasi va 10-bob).
8-mashq yechimi¶
from aiogram.filters import Command, CommandObject
@router.message(Command("echo"))
async def echo_cmd(message: Message, command: CommandObject) -> None:
if command.args is None:
await message.answer("Foydalanish: /echo matn")
else:
await message.answer(command.args)
CommandObject β buyruq tahlilini beradi. command.args β buyruqdan keyingi matn (/echo salom dunyo β args = "salom dunyo"). Argument bo'lmasa args is None. Men buni OFFLINE tekshirdim: /echo salom dunyo β "salom dunyo", /echo β "Foydalanish: /echo matn".
9-mashq yechimi¶
@router.message(F.text)
async def echo_handler(message: Message) -> None:
await message.answer(message.text)
# F.text dan keyin β matn bo'lmagan hammasi shu yerga tushadi
@router.message()
async def not_text_handler(message: Message) -> None:
await message.answer("Men faqat matn qaytaraman.")
F.text faqat matnli xabarga mos keladi. Rasm/stiker F.textga tushmaydi, shuning uchun filtri bo'sh @router.message() (har narsaga mos) handler ularni tutadi. Tartib muhim: F.text yuqorida.
10-mashq yechimi¶
import asyncio
from datetime import datetime
from unittest.mock import AsyncMock
from aiogram import Bot, Dispatcher, Router, F
from aiogram.filters import CommandStart
from aiogram.methods import SendMessage
from aiogram.types import Message, Chat, User, Update
def make_router() -> Router:
r = Router()
counts: dict[int, int] = {}
@r.message(CommandStart())
async def start(m: Message):
counts[m.from_user.id] = 0
await m.answer("Hisoblagich nolga tushdi.")
@r.message(F.text)
async def echo(m: Message):
counts[m.from_user.id] = counts.get(m.from_user.id, 0) + 1
await m.answer(f"Bu {counts[m.from_user.id]}-xabaringiz.")
return r
def upd(uid, text):
return Update(update_id=uid, message=Message(
message_id=uid, 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")
bot.session = AsyncMock()
dp = Dispatcher()
dp.include_router(make_router())
sent = []
async def rec(b, method, *a, **k): sent.append(method)
bot.session.side_effect = rec
for i, t in enumerate(["bir", "ikki", "uch"], start=1):
await dp.feed_update(bot, upd(i, t))
texts = [m.text for m in sent if isinstance(m, SendMessage)]
assert texts == ["Bu 1-xabaringiz.", "Bu 2-xabaringiz.", "Bu 3-xabaringiz."]
print("OK:", texts)
asyncio.run(main())
Men buni ishlatib ko'rdim β natija: OK: ['Bu 1-xabaringiz.', 'Bu 2-xabaringiz.', 'Bu 3-xabaringiz.'].
11-mashq yechimi¶
@router.message(CommandStart())
async def start_handler(message: Message) -> None:
u = message.from_user
await message.answer(
f"Salom, {u.first_name}!\n"
f"Sizning ID: {u.id}"
)
OFFLINE test (turli ismlar bilan):
def upd(uid, fname):
return Update(update_id=uid, message=Message(
message_id=uid, date=datetime.now(),
chat=Chat(id=1, type="private"),
from_user=User(id=uid, is_bot=False, first_name=fname), text="/start"))
# ... feed_update bilan upd(1, "Oqil"), upd(2, "Lola") yuborib,
# javoblarda "Salom, Oqil!" / "Sizning ID: 1" borligini tekshiring.
12-mashq yechimi¶
@router.message(F.text)
async def stats_handler(message: Message) -> None:
text = message.text
belgilar = len(text)
sozlar = len(text.split())
teskari = text[::-1]
await message.answer(
f"Belgilar: {belgilar}\n"
f"So'zlar: {sozlar}\n"
f"Teskari: {teskari}"
)
len(text) β belgilar soni, text.split() β bo'shliq bo'yicha so'zlarga ajratadi, text[::-1] β matnni teskari aylantiradi. Masalan "salom dunyo" β Belgilar: 11, So'zlar: 2, Teskari: oynud molas.
13-mashq yechimi¶
import asyncio
from datetime import datetime
from unittest.mock import AsyncMock
from aiogram import Bot, Dispatcher, Router, F
from aiogram.filters import CommandStart, Command
from aiogram.methods import SendMessage
from aiogram.types import Message, Chat, User, Update
# Router 1: buyruqlar
commands_router = Router()
@commands_router.message(CommandStart())
async def start(m: Message):
await m.answer("Salom! /help yozing.")
@commands_router.message(Command("help"))
async def helpc(m: Message):
await m.answer("Buyruqlar: /start, /help")
# Router 2: echo
echo_router = Router()
@echo_router.message(F.text)
async def echo(m: Message):
await m.answer(m.text)
def upd(uid, text):
return Update(update_id=uid, message=Message(
message_id=uid, 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")
bot.session = AsyncMock()
dp = Dispatcher()
dp.include_router(commands_router) # buyruqlar oldin
dp.include_router(echo_router) # echo keyin
sent = []
async def rec(b, method, *a, **k): sent.append(method)
bot.session.side_effect = rec
await dp.feed_update(bot, upd(1, "/start"))
await dp.feed_update(bot, upd(2, "/help"))
await dp.feed_update(bot, upd(3, "oddiy matn"))
texts = [m.text for m in sent if isinstance(m, SendMessage)]
assert texts == ["Salom! /help yozing.", "Buyruqlar: /start, /help", "oddiy matn"]
print("OK:", texts)
asyncio.run(main())
Bir nechta Router'ni include_router bilan ketma-ket ulash mumkin. Dispatcher ularni ulanish tartibida tekshiradi. Bu yerda commands_router oldin ulangani uchun /start va /help to'g'ri ishlaydi, qolgani echo_routerga tushadi. Bu β 03-bobdagi modulyar tuzilmaning asosi.
β¬ οΈ Oldingi: 01 β Telegram botlar va aiogram bilan tanishuv Β· π README Β· Keyingi: 03 β Handlerlar va Router β‘οΈ