09 β Middleware va Composer daraxti¶
β¬ οΈ Oldingi: 08 β Conversations β suhbatlar Β· π README Β· Keyingi: 10 β Sessiya va ma'lumotlar bazasi β‘οΈ
Bu bobda: grammY'ning yuragi β middleware bilan tanishamiz. Middleware bu
(ctx, next) => {}ko'rinishidagi funksiya: u har bir update'ni ko'radi vanext()chaqirib zanjirdagi keyingi funksiyaga uzatadi (yoki chaqirmay zanjirni to'xtatadi). Biz piyoz (onion) modelini βnext()dan oldingi kod kirishda, keyingi kod chiqishda ishlashini β o'rganamiz;bot.use(...)bilan o'rnatish va tartibning naqadar muhimligini ko'ramiz; amaliy middleware'lar yozamiz (logging, vaqt o'lchash, auth/admin tekshirish);ctxga ma'lumot qo'shib downstream'ga uzatamiz;Composerbilan handlerlarni daraxtga guruhlaymiz;bot.errorBoundarybilan xato chegarasini o'rnatamiz; va oddiy qo'lda throttling (flood-control) middleware quramiz. Bu boblardan keyin siz har qanday grammY botini "qatlam-qatlam" arxitektura sifatida ko'ra boshlaysiz.Halollik eslatmasi: Bu bobdagi BARCHA middleware mantig'i β piyoz ketma-ketligi,
next()to'xtatish,bot.usetartibi,ctx'ga property qo'shish,Composer,bot.errorBoundarydoirasi, qo'lda throttling vaawait next()unutish gotcha'si β soxtaUpdatenibot.handleUpdatega uzatib, chiqayotgan API chaqiruvlarini transformer bilan ushlab offline ishga tushirib tasdiqlangan (node _verify_09.mjs, 13/13 o'tdi). Jonli polling/xabar yuborish token va internet talab qiladi β u "illustrativ" deb belgilanadi.
Middleware nima?¶
Birinchi boblarda biz bot.command(...) va bot.on(...) bilan handlerlar yozdik. Aslida grammY'da hammasi middleware β handler ham middleware'ning maxsus turi. Middleware bu shunchaki funksiya:
async function mening(ctx, next) {
// ... ctx bilan biror ish ...
await next(); // zanjirdagi KEYINGI middleware'ga o'tish
}
Ikki argument:
ctxβ Context obyekti (update,ctx.from,ctx.replyva h.k. β 03-bobda ko'rgan edik).nextβ funksiya. Uni chaqirsangiz (await next()), boshqaruv zanjirdagi keyingi middleware'ga o'tadi. Chaqirmasangiz β zanjir shu yerda to'xtaydi, keyingi middleware va handlerlar umuman ishlamaydi.
Eslatma: Bu naqsh Express.js, Koa va boshqa Node freymvorklaridan tanish bo'lishi mumkin. Aslida grammY middleware tizimi aynan Koa'ning "onion" modelidan ilhomlangan. Node middleware bilan tanish bo'lsangiz, Node.js kitobi dagi bilim shu yerda ham asqotadi.
next() ni chaqirish β bu "vazifamni bajardim, endi navbat boshqalarga" degani. Chaqirmaslik β "men to'xtataman, boshqalar ko'rmasin" degani.
import { Bot } from "grammy";
const bot = new Bot(process.env.BOT_TOKEN);
bot.use(async (ctx, next) => {
console.log("Update keldi:", ctx.update.update_id);
await next(); // bularsiz pastdagi handler ISHLAMAYDI
});
bot.command("start", (ctx) => ctx.reply("Salom!"));
bot.start(); // illustrativ: jonli polling token talab qiladi
Piyoz (onion) modeli β eng muhim tushuncha¶
next() ni await bilan chaqirganda nima bo'ladi? Boshqaruv ichki qatlamga "kiradi", o'sha yerda hamma ish bajariladi, so'ng qaytib sizning funksiyangizga next() dan keyingi qatorga keladi. Ya'ni har bir middleware'ning ikki "fazasi" bor:
next()dan OLDINGI kod β bu kirish fazasi (update ichkariga kirayotganda).next()dan KEYINGI kod β bu chiqish fazasi (handler tugab, javob qaytayotganda).
Buni piyozning qatlamlariga o'xshatishadi: tashqaridan markazga (handler'ga) kirasiz, so'ng teskari yo'l bilan tashqariga chiqasiz.
Ikki middleware va bitta handler bilan ketma-ketlikni o'lchab ko'raylik:
const log = [];
bot.use(async (ctx, next) => {
log.push("A-kirish");
await next();
log.push("A-chiqish");
});
bot.use(async (ctx, next) => {
log.push("B-kirish");
await next();
log.push("B-chiqish");
});
bot.on("message", (ctx) => {
log.push("handler");
});
Bitta xabar kelganda log quyidagicha bo'ladi (offline tasdiqlangan):
Diqqat qiling: kirish tartib bo'yicha (A, B), chiqish esa teskari tartibda (B, A). Bu mantiqiy β eng oldin kirgan (A) eng oxirida chiqadi, xuddi steklarda bo'lgani kabi.
Diqqat:
awaitninext()oldida UNUTMANG.await next()o'rniga shunchakinext()yozsangiz, "chiqish" fazasi handler tugashini KUTMAY ishlaydi β vaqt o'lchash, resurs tozalash, transaksiya yopish kabi narsalar buziladi. Buni bobdagi gotcha bo'limida ko'ramiz.
bot.use(...) va tartib¶
bot.use(...) middleware'ni ro'yxat oxiriga qo'shadi. Update kelganda grammY ularni qo'shilgan tartibda yuqoridan pastga aylanib chiqadi. Demak, tartib muhim: kim oldin yozilsa, o'sha oldin ko'radi.
Amaliy qoida: logging, auth va sessiya kabi "hammaga tegishli" middleware'lar eng oldinga qo'yiladi, konkret handlerlar pastga. Masalan, agar siz avval bot.command("start") ni yozib, keyin logging middleware'ni qo'ysangiz β /start log'lanmay qoladi, chunki command handler next() ni chaqirmasdan zanjirni to'xtatadi.
// TO'G'RI tartib:
bot.use(loggingMiddleware); // 1. har update'ni yozadi (next chaqiradi)
bot.use(authMiddleware); // 2. ruxsatni tekshiradi
bot.command("panel", panel); // 3. konkret handler
Eslatma: Handlerlar (
bot.command,bot.hears,bot.on) odatdanext()ni o'zi chaqirmaydi β mos kelsa, ishlaydi va zanjirni to'xtatadi. Shuning uchun "birinchi mos kelgan handler g'olib" qoidasi amal qiladi (03-bobda ko'rgan edik). Bir nechta handler ishlashini xohlasangiz, ulardannext()ni qo'lda chaqirishingiz kerak.
Auth/admin tekshirish: next() chaqirmasdan to'xtatish¶
Eng foydali namuna β ruxsatni tekshiruvchi middleware. Admin bo'lmagan foydalanuvchini darvozada to'xtatamiz:
const ADMIN_ID = 999; // o'zingizning Telegram ID'ingiz (10-bobda DB'dan o'qiymiz)
bot.use(async (ctx, next) => {
if (ctx.from?.id !== ADMIN_ID) {
await ctx.reply("Ruxsat yo'q");
return; // next() YO'Q -> zanjir to'xtaydi, pastdagi handler ishlamaydi
}
await next(); // admin -> davom etamiz
});
bot.command("panel", (ctx) => ctx.reply("Admin paneli"));
Offline sinovda oddiy foydalanuvchi "Ruxsat yo'q" oldi, admin esa "Admin paneli" oldi β aynan kutilgandek.
Diqqat: Bu middleware BARCHA handlerlardan oldin turibdi, demak u butun botni admin uchun yopadi. Agar faqat bir qism handlerlarni himoyalamoqchi bo'lsangiz β pastda ko'radigan
Composeryokibot.errorBoundarydoirasini ishlating, butunbotga emas.
Amaliy middleware'lar¶
1. Logging β har update'ni yozish¶
bot.use(async (ctx, next) => {
const id = ctx.update.update_id;
const text = ctx.message?.text ?? "(matnsiz)";
console.log(`[${id}] ${ctx.from?.first_name}: ${text}`);
await next();
});
Bu eng oddiy va eng foydali middleware. Productionda console.log o'rniga to'liq logger (pino, winston) ishlatiladi β buni Node.js kitobida ko'rgansiz.
2. Vaqt o'lchash β next() oldin/keyin farqi¶
Piyoz modeli tufayli javob qancha vaqt olganini o'lchash juda oson: next() dan oldin vaqtni yozamiz, keyin farqni hisoblaymiz.
bot.use(async (ctx, next) => {
const boshlanish = Date.now();
await next(); // butun ichki zanjir shu yerda ishlaydi
const ketdi = Date.now() - boshlanish;
console.log(`Update ${ctx.update.update_id} ${ketdi}ms da ishladi`);
});
ketdi doimo >= 0 β chunki u handler tugagandan keyin hisoblanadi. Bu sekin handlerlarni topishda asqotadi.
3. Auth/admin β yuqorida ko'rdik¶
Bu uchovi β logging, vaqt o'lchash, auth β har qanday jiddiy botda uchraydi. Ularni odatda alohida fayllarga ajratib, modul sifatida import qilasiz (11-bobda loyiha tuzilishini ko'ramiz).
ctx ga ma'lumot qo'shish (downstream'ga uzatish)¶
Tez-tez upstream (yuqori) middleware'da biror narsani hisoblab, uni pastdagi handlerga uzatish kerak bo'ladi. Masalan: foydalanuvchi admin'mi, uning DB yozuvi, til sozlamasi. Yechim β ctx obyektiga o'z propertyingizni qo'shish:
bot.use(async (ctx, next) => {
ctx.role = (ctx.from?.id === ADMIN_ID) ? "admin" : "user"; // o'z propertysi
await next();
});
bot.on("message", (ctx) => {
ctx.reply(`Sizning rolingiz: ${ctx.role}`); // downstream o'qiydi
});
Bu ishlaydi, chunki butun zanjir uchun bitta ctx obyekti ishlatiladi β uni "yuqorida" boyitsangiz, "pastda" o'qiy olasiz.
Diqqat β grammY'da
ctx.stateYO'Q! Telegraf'da maxsusctx.stateobyekti bor edi va ko'p o'quvchilar internetda o'shani uchratadi. grammY'da bunday built-in property mavjud emas β yangictxdactx.stateundefinedbo'ladi (buni offline tekshirdik:assert.equal(ctx.state, undefined)o'tdi). grammY'da siz to'g'ridan-to'g'rictx.role,ctx.userkabi o'z propertyingizni qo'shasiz. Agar Telegraf misolidactx.state.x = ...ko'rsangiz β bu grammY emas, ehtiyot bo'ling.Eslatma β TypeScript bilan tiplash: Sof JS'da
ctx.role = ...to'g'ridan-to'g'ri ishlaydi. TypeScript'da esactxga yangi propertyni context flavor orqali tiplaysiz (type MyContext = Context & { role: string }), shunda muharrir avtokomplit beradi va xatoni ushlaydi. Bu kitob JS'da bo'lgani uchun biz oddiy yo'lni ishlatamiz; tiplangan kontekst va loyiha tuzilishini 11-bobda ko'ramiz. Bu β TS'ning "qo'shimcha foydasi", JS uchun shart emas.
Composer bilan guruhlash¶
Bot kattalashganda hamma handlerni bitta faylga tiqish noqulay. Composer β bu handlerlarni guruhlaydigan "quticha". Composer ham xuddi bot kabi .command, .on, .use metodlariga ega (aslida Bot ning o'zi Composer dan meros oladi!). Guruhni yig'ib bo'lgach, uni bot.use(composer) bilan ulaysiz.
import { Composer } from "grammy";
const adminComposer = new Composer();
adminComposer.command("panel", (ctx) => ctx.reply("Admin paneli"));
adminComposer.command("ban", (ctx) => ctx.reply("Foydalanuvchi bloklandi"));
const userComposer = new Composer();
userComposer.command("start", (ctx) => ctx.reply("Salom!"));
userComposer.on("message:text", (ctx) => ctx.reply("Matningizni oldim"));
bot.use(adminComposer); // butun guruhni botga ulaymiz
bot.use(userComposer);
Natijada bot daraxt ko'rinishini oladi: ildizda bot, undan tarmoqlar (Composer'lar va middleware'lar), tarmoqlar uchida konkret handlerlar.
Composer ichida ham xuddi bot dagidek tartib va next() qoidalari amal qiladi: birinchi mos handler ishlaydi va to'xtatadi (offline tasdiqlangan). Bu modullashning asosiy quroli β har bir mavzuni (admin, user, to'lov, guruh) o'z Composer'iga ajratib, alohida faylda saqlaysiz va bot.use(...) bilan ulaysiz. Katta loyihada bu naqshni 11-bobda chuqur ko'ramiz.
Eslatma:
Composerga middleware ham bog'lash mumkin:adminComposer.use(adminAuth)β shundaadminAuthfaqat o'sha Composer ichidagi handlerlarga ta'sir qiladi, butun botga emas. Bu "qisman himoya" uchun toza yo'l.
bot.errorBoundary β xato chegarasi¶
bot.catch(...) (08-bobdan) β bu global xato tuzog'i: butun bot bo'ylab tutilmagan har qanday xatoni ushlaydi. Lekin ba'zan siz xatoni lokal β botning faqat bir qismida β ushlamoqchisiz: masalan, "to'lov moduli yiqilsa, qolgan bot ishlashda davom etsin". Buning uchun bot.errorBoundary(handler) bor.
errorBoundary yangi Composer qaytaradi. O'sha Composer'ga bog'langan handlerlar ichida sodir bo'lgan xato chegara funksiyasiga boradi, global bot.catch ga emas.
const himoyalangan = bot.errorBoundary((err, next) => {
// err.error β asl xato (08-bobdagi kabi)
console.error("To'lov moduli xato berdi:", err.error.message);
// next() chaqirmasak -> xato shu yerda yutiladi, bot ishlashda davom etadi
});
himoyalangan.command("tolov", (ctx) => {
throw new Error("portladi"); // bu xato himoyalangan chegarada ushlanadi
});
// BU handler chegaradan TASHQARIDA β uning xatosi global bot.catch'ga boradi
bot.on("message", (ctx) => {
throw new Error("tashqi xato");
});
Offline sinovda biz aniq ko'rdik:
himoyalangan.on(...)ichidagithrowβ chegara funksiyasi ushladi, global tuzoqqa chiqmadi.bot.on(...)(chegaradan tashqari) ichidagithrowβ chegarani chetlab o'tdi va yuqoriga otildi.
Chegara funksiyasida next() ni chaqirsangiz, xatoni "yuqoriga" (keyingi tuzoqqa yoki bot.catch ga) qayta uzatasiz; chaqirmasangiz β xato yutiladi (bot davom etadi).
Diqqat β
bot.catchqachon ishlaydi:bot.catch(...)global tuzog'i botbot.start()yoki@grammyjs/runnerorqali ishlayotganda tutilmagan xatolarni ushlaydi. Agar siz update'ni qo'ldaawait bot.handleUpdate(update)bilan uzatsangiz (masalan test'da yoki webhook'da o'zingiz boshqarsangiz), xatoBotErrorsifatida yuqoriga otiladi β unitry/catchbilan o'zingiz tutishingiz kerak. Buni offline tekshirdik. Webhook'da bu nuansni 13-bobda ko'ramiz.
Throttling (flood-control) β qo'lda¶
Foydalanuvchi botga juda tez-tez xabar yuborsa (flood), uni cheklash kerak. Bu g'oyani qo'lda middleware bilan amalga oshirish mumkin: har bir foydalanuvchining oxirgi qabul qilingan vaqtini eslab, juda tez kelgan so'rovni tashlab yuboramiz.
const oxirgi = new Map(); // userId -> oxirgi qabul qilingan vaqt (ms)
const MIN_MS = 1000; // bir foydalanuvchiga sekundiga 1 ta
bot.use(async (ctx, next) => {
const id = ctx.from?.id;
if (id === undefined) return await next();
const hozir = Date.now();
const avvalgi = oxirgi.get(id);
if (avvalgi !== undefined && hozir - avvalgi < MIN_MS) {
return; // juda tez! next() chaqirmaymiz -> jim tashlab yuboramiz
}
oxirgi.set(id, hozir);
await next();
});
Offline sinovda (vaqtni 0, 500, 1500 ms qilib boshqarib): 0ms o'tdi, 500ms bloklandi (chunki 1000ms o'tmagan), 1500ms yana o'tdi. Aynan kutilgandek.
Diqqat: Bu oddiy versiya
Mapni xotirada saqlaydi β bot qayta ishga tushsa, hammasi unutiladi vaMapcheksiz o'sib boraversa, xotira "sizib" ketishi mumkin (vaqti-vaqti bilan eski yozuvlarni tozalash kerak). Production uchun esa tayyor plagin bor:Anti-eskirish: Jiddiy flood-control uchun
@grammyjs/transformer-throttlerplaginini ishlating β u Telegram'ning chiqayotgan so'rovlarini Bottleneck navbati bilan boshqaradi va rate-limit'ga tushib qolishingizning oldini oladi. Bu alohida paket (botga emas,bot.apitransformeriga ulanadi). Aniq import va sozlamalarni hujjatdan oling: grammy.dev/plugins/transformer-throttler. Bu yerda men uning API'sini ixtiro qilmayman β rasmiy hujjatga qarang. Ommaviy tarqatish (broadcast) va@grammyjs/auto-retryni 15-bobda chuqur ko'ramiz.
API transformer β chiquvchi so'rovlarni o'zgartirish (qisqacha)¶
Yuqoridagi middleware kiruvchi update'larni qayta ishlaydi. Bundan farqli ravishda API transformer chiquvchi so'rovlarni (Telegram'ga yuborilayotgan sendMessage va h.k.) ushlaydi va o'zgartiradi:
bot.api.config.use((prev, method, payload, signal) => {
// bu yerda chiqayotgan so'rovni ko'rish/o'zgartirish mumkin
return prev(method, payload, signal); // davom ettiramiz
});
Bu bobdagi offline sinovlarimiz aynan shu transformer mexanizmidan foydalanib chiqayotgan API chaqiruvlarini ushlaydi (tarmoqqa chiqmasdan). Real misol β @grammyjs/auto-retry: u xato bo'lgan so'rovni avtomatik qayta yuboradi. API transformer'larni va auto-retry'ni 15-bobda chuqur ko'ramiz.
Eslatma: Esda tuting β middleware (
bot.use) = kiruvchi update'lar uchun; transformer (bot.api.config.use) = chiquvchi API so'rovlari uchun. Ikkalasi ham "zanjir" g'oyasiga asoslanadi, lekin har xil oqimda ishlaydi.
Tez-tez uchraydigan xatolar¶
| Xato | Sabab | Yechim |
|---|---|---|
| Handler umuman ishlamayapti | Yuqoridagi middleware next() ni chaqirmagan |
Middleware oxirida await next() chaqiring (to'xtatish ataylab bo'lmasa) |
Logging /start ni ko'rmayapti |
bot.command("start") logging'dan OLDIN yozilgan |
Logging middleware'ni eng oldinga (bot.use bilan birinchi) qo'ying |
| "chiqish" kodi noto'g'ri vaqtda ishlaydi | await next() o'rniga next() yozilgan |
next() oldida har doim await yozing |
ctx.state is undefined |
grammY'da ctx.state yo'q (Telegraf'niki) |
To'g'ridan-to'g'ri ctx.myProp = ... qo'shing |
errorBoundary xatoni ushlamayapti |
Handler chegara doirasidan TASHQARIDA bog'langan | Handlerni errorBoundary qaytargan Composer'ga bog'lang |
bot.catch handleUpdate da ishlamayapti |
handleUpdate xatoni yuqoriga otadi |
handleUpdate ni try/catch ga oling, yoki bot.start()/runner ishlating |
Throttling Map xotira yeb boryapti |
Eski yozuvlar hech qachon tozalanmaydi | Davriy tozalang yoki transformer-throttler plaginini ishlating |
Mashqlar¶
Quyidagi mashqlarning ko'pi offline tekshiriladi β
bot.handleUpdate(update)ga soxta update uzatib,bot.api.config.use(...)transformer bilan chiqayotgan chaqiruvlarni ushlaysiz yokilogmassivining tartibiniassertqilasiz. Buyruq update'igaentities:[{type:"bot_command",offset:0,length:N}]qo'shishni unutmang.
Oson¶
-
Piyoz tartibi. Ikki
bot.usemiddleware (A va B) va bittabot.on("message")handler yozing. Har birilogmassiviga"X-kirish"/"X-chiqish"qo'shsin. Bitta xabar uzating valogaynan["A-kirish","B-kirish","handler","B-chiqish","A-chiqish"]ekanini tasdiqlang. -
next()ni to'xtatish.next()ni chaqirmaydigan middleware yozing. Undan keyinbot.on("message")handler qo'ying. Xabar uzating va handler ISHLAMAGANINI tasdiqlang (logda faqat middleware bor). -
Logging. Har update'ni
loggaupdate <id>: <text>ko'rinishida yozadigan middleware yozing (next()ni chaqiring). Ikki xabar uzating va tartib saqlanganini tasdiqlang. -
ctxga property. Upstream middleware'dactx.salom = "Assalom"qo'ying. Handlerctx.salomnictx.replybilan jo'natsin. Transformer bilan chiqqan matn"Assalom"ekanini tasdiqlang.
O'rta¶
-
Vaqt o'lchash.
next()atrofidaDate.now()farqini o'lchaydigan middleware yozing va o'lchangan qiymatni tashqi o'zgaruvchiga saqlang. Xabar uzating va qiymat>= 0ekanini tasdiqlang. -
Admin darvozasi.
ADMIN_IDga teng bo'lmagan foydalanuvchini"Ruxsat yo'q"bilan to'xtatadigan middleware yozing.bot.command("panel")"Admin paneli"qaytarsin. Oddiy va admin foydalanuvchidan/paneluzatib, ikki xil javobni tasdiqlang. -
Composerguruhi.Composeryarating, unga.command("ping")("pong"qaytaradi) bog'lang,bot.use(composer)qiling./pinguzatib"pong"kelganini tasdiqlang. -
ctx.stateyo'qligi. Middleware ichidactx.stateningundefinedekaniniassertqiling, so'ngctx.rolega qiymat bering. Handlerctx.roleni qaytarsin. Tasdiqlang. (grammY'dactx.stateyo'qligini eslang.)
Qiyin¶
-
errorBoundaryichi.bot.errorBoundarychegarasi yarating; chegara funksiyasilogga"ushladi"qo'shsin vanext()chaqirmasin. Chegaragathrowqiladigan.on("message")handler bog'lang. Xabar uzating valogda"ushladi"borligini, xato yuqoriga chiqmaganini tasdiqlang. -
errorBoundarytashqarisi. 9-mashqdagi chegaradan TASHQARIDAthrowqiladiganbot.on("message")handler bog'lang.handleUpdatenitry/catchga oling va xato yuqoriga otilganini (chegara ushlamaganini) tasdiqlang. -
Qo'lda throttling. Foydalanuvchi bo'yicha
MIN_MScheklovi bilan throttling middleware yozing. Vaqtnictxorqali boshqarib (yokiDate.nowni mock qilib),0/500/1500ms da uchta xabar uzating va faqat 1- va 3- so'rov o'tganini tasdiqlang. -
awaitunutish gotcha'si. Bir middleware yozing:log.push("kirish")->next()(await SIZ) ->log.push("chiqish"). Handlerawait Promise.resolve()dan keyinlog.push("handler")qilsin.logning["kirish","chiqish","handler"]ekanini (ya'ni tartib buzilganini) tasdiqlang, so'ngawait next()qo'shib to'g'rilang. -
Composer ichida tartib. Bitta
Composerga ikki.on("message:text")handler bog'lang: birinchisi.filter((ctx) => ctx.msg.text.startsWith("a"), ...)."abc"uzatib, faqat filtr handleri ishlaganini (umumiy handler emas) tasdiqlang.
Yechimlar
Quyidagi yechimlar
_verify_09.mjsdagi naqsh bilan offline ishga tushiriladi. Har bir yechim mustaqilmakeBot()(transformer +botInfo) vamkText(...)yordamchilaridan foydalanadi (bob boshidagi halollik eslatmasiga qarang). Qisqartirish uchun shu ikki yordamchi takrorlanmaydi.
1-mashq yechimi¶
const { bot } = makeBot();
const log = [];
bot.use(async (ctx, next) => { log.push("A-kirish"); await next(); log.push("A-chiqish"); });
bot.use(async (ctx, next) => { log.push("B-kirish"); await next(); log.push("B-chiqish"); });
bot.on("message", (ctx) => { log.push("handler"); });
await bot.handleUpdate(mkText("salom"));
assert.deepEqual(log, ["A-kirish", "B-kirish", "handler", "B-chiqish", "A-chiqish"]);
Piyoz modeli: kirish to'g'ri tartibda, chiqish teskari tartibda. Bu butun bobning poydevori.
2-mashq yechimi¶
const { bot } = makeBot();
const log = [];
bot.use((ctx, next) => { log.push("darvoza"); /* next() YO'Q */ });
bot.on("message", (ctx) => { log.push("handler"); });
await bot.handleUpdate(mkText("salom"));
assert.deepEqual(log, ["darvoza"]); // handler ISHLAMADI
next() chaqirilmagani uchun zanjir darvozada to'xtadi β handler log ga umuman qo'shilmadi.
3-mashq yechimi¶
const { bot } = makeBot();
const log = [];
bot.use(async (ctx, next) => {
log.push(`update ${ctx.update.update_id}: ${ctx.message?.text ?? ""}`);
await next();
});
bot.on("message", (ctx) => {});
await bot.handleUpdate(mkText("birinchi", 10));
await bot.handleUpdate(mkText("ikkinchi", 11));
assert.deepEqual(log, ["update 10: birinchi", "update 11: ikkinchi"]);
Logging middleware next() dan oldin (kirish fazasida) yozadi, shuning uchun update'lar kelish tartibida log'lanadi.
4-mashq yechimi¶
const { bot, calls } = makeBot();
bot.use(async (ctx, next) => { ctx.salom = "Assalom"; await next(); });
bot.on("message", (ctx) => ctx.reply(ctx.salom));
await bot.handleUpdate(mkText("hi"));
const c = calls.find((x) => x.method === "sendMessage");
assert.equal(c.payload.text, "Assalom");
Butun zanjir uchun bitta ctx ishlatilgani uchun upstream qo'ygan property downstream'da o'qiladi.
5-mashq yechimi¶
const { bot } = makeBot();
let olchangan = -1;
bot.use(async (ctx, next) => {
const t = Date.now();
await next();
olchangan = Date.now() - t;
});
bot.on("message", async (ctx) => { /* ish */ });
await bot.handleUpdate(mkText("salom"));
assert.ok(olchangan >= 0);
Vaqt next() dan keyin (chiqish fazasida) hisoblanadi β handler tugagach. Shuning uchun u doimo manfiy emas.
6-mashq yechimi¶
const { bot, calls } = makeBot();
const ADMIN_ID = 999;
bot.use(async (ctx, next) => {
if (ctx.from?.id !== ADMIN_ID) { await ctx.reply("Ruxsat yo'q"); return; }
await next();
});
bot.command("panel", (ctx) => ctx.reply("Admin paneli"));
await bot.handleUpdate(mkText("/panel", 1, 777)); // oddiy user
await bot.handleUpdate(mkText("/panel", 2, 999)); // admin
const texts = calls.filter((c) => c.method === "sendMessage").map((c) => c.payload.text);
assert.deepEqual(texts, ["Ruxsat yo'q", "Admin paneli"]);
Auth middleware eng oldinda turgani uchun oddiy user darvozada to'xtaydi, admin esa next() orqali handler'ga o'tadi.
7-mashq yechimi¶
const { bot, calls } = makeBot();
const composer = new Composer();
composer.command("ping", (ctx) => ctx.reply("pong"));
bot.use(composer);
await bot.handleUpdate(mkText("/ping"));
const c = calls.find((x) => x.method === "sendMessage");
assert.equal(c.payload.text, "pong");
Composer handlerlarni guruhlaydi; bot.use(composer) butun guruhni botga ulaydi. (import { Composer } from "grammy"; kerak.)
8-mashq yechimi¶
const { bot, calls } = makeBot();
bot.use(async (ctx, next) => {
assert.equal(ctx.state, undefined); // grammY'da ctx.state YO'Q
ctx.role = ctx.from?.id === 999 ? "admin" : "user";
await next();
});
bot.on("message", (ctx) => ctx.reply(`Rol: ${ctx.role}`));
await bot.handleUpdate(mkText("salom", 1, 999));
const c = calls.find((x) => x.method === "sendMessage");
assert.equal(c.payload.text, "Rol: admin");
ctx.state undefined (Telegraf'ning property'si, grammY'da yo'q) β shuning uchun o'z ctx.role propertyimizni ishlatamiz.
9-mashq yechimi¶
const { bot } = makeBot();
const log = [];
const himoyalangan = bot.errorBoundary((err, next) => {
log.push("ushladi: " + err.error.message); // next() YO'Q -> yutiladi
});
himoyalangan.on("message", (ctx) => { throw new Error("portladi"); });
await bot.handleUpdate(mkText("salom")); // yuqoriga chiqmaydi
assert.deepEqual(log, ["ushladi: portladi"]);
Chegara ichidagi throw chegara funksiyasiga boradi; next() chaqirilmagani uchun xato yutiladi va handleUpdate muvaffaqiyatli yakunlanadi.
10-mashq yechimi¶
const { bot } = makeBot();
const log = [];
const himoyalangan = bot.errorBoundary((err, next) => { log.push("boundary"); });
himoyalangan.command("xavfsiz", (ctx) => ctx.reply("ok"));
bot.on("message", (ctx) => { throw new Error("tashqi xato"); }); // chegaradan TASHQARIDA
let thrown = null;
try { await bot.handleUpdate(mkText("salom")); }
catch (err) { thrown = err; }
assert.equal(log.length, 0); // boundary ushlamadi
assert.equal(thrown.error.message, "tashqi xato"); // yuqoriga otildi
errorBoundary faqat O'Z doirasidagi handlerlarni qamrab oladi. Tashqaridagi xato chegarani chetlab handleUpdate orqali yuqoriga otiladi (bot.catch esa bot.start()/runner uchun).
11-mashq yechimi¶
const { bot, calls } = makeBot();
const oxirgi = new Map();
const MIN_MS = 1000;
bot.use(async (ctx, next) => { ctx.now = ctx.update.__now ?? 0; await next(); }); // testda vaqt
bot.use(async (ctx, next) => {
const id = ctx.from?.id;
const prev = oxirgi.get(id);
if (prev !== undefined && ctx.now - prev < MIN_MS) return; // juda tez
oxirgi.set(id, ctx.now);
await next();
});
bot.on("message", (ctx) => ctx.reply("qabul qilindi"));
await bot.handleUpdate({ ...mkText("a", 1), __now: 0 }); // o'tadi
await bot.handleUpdate({ ...mkText("b", 2), __now: 500 }); // bloklanadi
await bot.handleUpdate({ ...mkText("c", 3), __now: 1500 }); // o'tadi
const texts = calls.filter((x) => x.method === "sendMessage").map((x) => x.payload.text);
assert.deepEqual(texts, ["qabul qilindi", "qabul qilindi"]); // 2 ta o'tdi
Testda vaqtni update.__now orqali boshqaramiz (real botda Date.now()). 500ms so'rov MIN_MS ichida bo'lgani uchun next() chaqirilmay bloklanadi.
12-mashq yechimi¶
const { bot } = makeBot();
const log = [];
bot.use(async (ctx, next) => {
log.push("kirish");
next(); // XATO: await UNUTILDI
log.push("chiqish"); // handler tugashini KUTMAYDI
});
bot.on("message", async (ctx) => { await Promise.resolve(); log.push("handler"); });
await bot.handleUpdate(mkText("salom"));
assert.deepEqual(log, ["kirish", "chiqish", "handler"]); // tartib BUZILGAN!
// To'g'risi: `await next()` -> ["kirish","handler","chiqish"]
await unutilgani uchun "chiqish" handler tugashini kutmadi va undan oldin yozildi. await next() qo'ysangiz tartib ["kirish","handler","chiqish"] ga to'g'rilanadi.
13-mashq yechimi¶
const { bot } = makeBot();
const composer = new Composer();
const log = [];
composer.on("message:text").filter(
(ctx) => ctx.msg.text.startsWith("a"),
(ctx) => { log.push("a-filter"); }
);
composer.on("message:text", (ctx) => { log.push("umumiy"); });
bot.use(composer);
await bot.handleUpdate(mkText("abc"));
assert.deepEqual(log, ["a-filter"]); // umumiy handler ishlamadi
Composer ichida ham "birinchi mos handler g'olib" qoidasi amal qiladi: "abc" filtrga mos kelgani uchun filtr handleri ishladi va next() chaqirmasdan zanjirni to'xtatdi β umumiy handler'ga yetib bormadi.
β¬ οΈ Oldingi: 08 β Conversations β suhbatlar Β· π README Β· Keyingi: 10 β Sessiya va ma'lumotlar bazasi β‘οΈ