Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Birinchi bot
- 2.1 Bot va context
- 2.2 ctx.match — buyruqdan keyingi matn
- 2.3 Qaysi ishlovchi ishlaydi?
- 3. Middleware zanjiri
- 3.1 G'oya: piyoz qatlamlari
- 3.2 Kanon tajribasi: haqiqiy grammY bilan
- 3.3 Composer va bot.filter
- 4. Filter query'lar
- 4.1 Sintaksis
- 4.2 Tiplar bilan bog'liqligi
- 4.3 Buyruqlar
- 5. Xatolar: bot.catch
- 5.1 Ushlanmagan xato botni to'xtatadi
- 5.2 bot.catch bilan
- 6. Plagin tizimi
- 7. grammY va Telegraf
- 8. Hujumchi nigohi
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): qaysi ishlovchi?
- 2-mashq (o'rta): buyruq yordamchisi
- 3-mashq (qiyin): bot.catch ning nusxasi
- 11. Loyiha qadami: eslatma boti skeleti
- 11.1 Hisobni ulash: deep link
- 11.2 /vazifalar va bot.catch
- 11.3 Testlar
- 12. Real ishda
- Xulosa
- Manbalar
grammY bilan bot yozish: context, middleware va filter query'lar
Qisqacha: grammY — Telegram Bot API ustidagi TypeScript freymvork (framework). U
getUpdatessiklini, update turlarini ajratishni va javob yuborishni o'z zimmasiga oladi. Siz faqat ishlovchi yozasiz:bot.command("start", ...),bot.on("message:text", ...). Har update middleware zanjiri bo'ylab yuqoridan pastga o'tadi,next()uni keyingisiga uzatadi.bot.catchni albatta qo'ying: biz sinaganimizda usiz bitta xato butun botni to'xtatdi.
Bu darsda
Bot, context (ctx) vactx.replybilan birinchi botni yozasiz.- Middleware zanjiri qanday ishlashini kanon tajribasi va o'zingizning kichik nusxangiz bilan ko'rasiz.
- Filter query'lar (
message:text,:text,message:photo), buyruqlar vactx.matchni ishlatasiz. Composervabot.filterbilan bir guruh ishlovchini ajratasiz.- Xatolarni
bot.catchbilan ushlaysiz va plagin tizimini tushunasiz. - Loyiha uchun
vazifalareslatma botining skeletini yozasiz: deep link bilan hisobni ulash va/vazifalar.
Oldin bilishingiz kerak: Telegram Bot API asoslari (Update, getUpdates, offset, webhook), Middleware va request lifecycle, ES Modules Node'da.
Oldingi darsda Bot API bilan "qo'lda" gaplashdik: getUpdates ni chaqirdik, offset ni o'zimiz sanadik, javobni fetch bilan yubordik. Bugun shu ishning hammasini freymvork'ga topshiramiz.
1. Nega bu kerak?
Xom Bot API bilan bot yozishni tasavvur qiling. Har update uchun siz:
getUpdatesnitimeoutbilan chaqirasiz vaoffsetni to'g'ri oshirasiz.- Update ichida nima borligini aniqlaysiz:
message,callback_query,inline_queryyoki boshqasi. - Xabar matn bo'lsa — buyruqmi (
/start) yoki oddiy matnmi, tekshirasiz. - Javobni
sendMessagebilan yuborasiz:chat_idni o'zingiz topasiz. - Xato bo'lsa (tarmoq, 429, 403) — nima qilishni hal qilasiz.
Bu kod har botda bir xil. Freymvork shu takroriy qismni oladi, siz esa faqat "nima qilish kerak" degan qismni yozasiz.
O'xshatish: oshxona. Xom Bot API — har buyurtmani o'zingiz eshikdan olib, o'zingiz o'qib, o'zingiz olib chiqishingiz. Freymvork — ofitsiant: buyurtmani qabul qiladi, kerakli oshpazga beradi, tayyor taomni stolga olib boradi. Oshpaz (sizning ishlovchingiz) faqat pishiradi.
Node.js'da ikki mashhur bot freymvork bor: grammY va Telegraf. Kanon grammY 1.46.0 ni ishlatdi.
2. Birinchi bot
2.1 Bot va context
npm i grammy@1.46.0Bot tokenini BotFather beradi (oldingi darsda ko'rgansiz). Token — parol: kodga yozilmaydi, muhit o'zgaruvchisidan olinadi.
import { Bot } from "grammy";
const bot = new Bot(process.env.BOT_TOKEN);
bot.command("start", async (ctx) => {
console.log("match:", JSON.stringify(ctx.match));
await ctx.reply(`Salom, ${ctx.from?.first_name}!`);
});
bot.on("message:text", async (ctx) => {
console.log("chat:", ctx.chat.id, ctx.chat.type,
"| matn:", ctx.message.text);
await ctx.reply(`Siz yozdingiz: ${ctx.message.text}`);
});
bot.start();Bu yerda uchta yangi narsa bor:
new Bot(token)— bot obyekti. Ichida API klienti (bot.api) va ishlovchilar ro'yxati.- Context (
ctx) — bitta update haqidagi hamma narsa bitta obyektda.ctx.message— xabar,ctx.from— kim yozgan,ctx.chat— qaysi chat.ctx.reply(...)— "shu chatga javob yoz" degani:chat_idni grammY o'zi qo'yadi. bot.start()— long polling'ni boshlaydi: grammYgetUpdatesni o'zi chaqiradi vaoffsetni o'zi oshiradi.
Biz bu botni haqiqiy Telegram bilan emas, kanondagi lokal soxta Bot API serveri (mock) bilan ishga tushirdik. grammY'ga client: { apiRoot: tg.url } berildi — so'rovlar api.telegram.org ga emas, kompyuterdagi mock'ka ketdi. "Foydalanuvchi" Aziz (chat 42) botga /start, /start ulash-123 va non olish deb yozdi:
match: ""
javob: Salom, Aziz!
match: "ulash-123"
chat: 42 private | matn: non olish
javob: Siz yozdingiz: non olishDiqqat: Bu bo'limdagi barcha chiqishlar lokal simulyatordan. Haqiqiy Telegram serveri, haqiqiy token va BotFather bu kursda sinalmagan (2026-10-27). Mock hujjatdagi qoidalarni takrorlaydi, lekin u Telegram'ning o'zi emas.
2.2 ctx.match — buyruqdan keyingi matn
/start ulash-123 da ctx.match — "ulash-123". Buyruqdan keyingi hamma narsa shu yerga tushadi. Bu deep link uchun asos: https://t.me/sizning_bot?start=ulash-123 havolasi ochilganda Telegram botga aynan /start ulash-123 yuboradi. Telegram hujjatiga ko'ra parametr 64 belgigacha va faqat A-Z, a-z, 0-9, _, - belgilaridan iborat bo'ladi. Loyiha qadamida shu bilan hisobni ulaymiz.
2.3 Qaysi ishlovchi ishlaydi?
/start yozilganda nega message:text ishlovchisi ishlamadi? Axir /start ham matn-ku. Javob — middleware zanjirida.
Tekshirib ko'ring:
bot.on("message:text", ...)nibot.command("start", ...)dan oldin yozsak,/startga nima bo'ladi?
Javob
Bot "Siz yozdingiz: /start" deb javob beradi, salom esa chiqmaydi. Ishlovchilar qo'shilgan tartibda tekshiriladi. message:text birinchi mos keladi, javob beradi va next() ni chaqirmaydi — update keyingi ishlovchiga yetmaydi. Shuning uchun aniq ishlovchilar (buyruqlar) yuqorida, umumiylari (har qanday matn) pastda turadi.
3. Middleware zanjiri
3.1 G'oya: piyoz qatlamlari
Middleware va request lifecycle darsida Express'da (req, res, next) ni ko'rgansiz. grammY'da shakl (ctx, next), g'oya bir xil. Har bot.use, bot.on, bot.command zanjirga bitta middleware qo'shadi. Update birinchisiga kiradi. Middleware yo o'zi ishlaydi, yo await next() bilan keyingisiga uzatadi.
await next() dan keyin yozilgan kod qachon ishlaydi? Keyingi hamma middleware tugaganidan keyin. Shuning uchun log yozuvchi middleware update'ni "kirishda" ham, "chiqishda" ham ko'radi — xuddi piyoz qatlamlari kabi.
Buni tushunish uchun grammY ichidagi g'oyaning juda soddalashtirilgan nusxasini yozamiz. Bu grammY kodi emas — faqat mexanizm:
// middleware zanjirining soddalashtirilgan nusxasi
function zanjir(ishlovchilar) {
return async (ctx) => {
const yurgiz = async (i) => {
const f = ishlovchilar[i];
if (f) await f(ctx, () => yurgiz(i + 1));
};
await yurgiz(0);
};
}
const log = async (ctx, next) => {
console.log(`> ${ctx.matn}`);
await next();
console.log(`< ${ctx.matn}`);
};
const start = async (ctx, next) => {
if (ctx.matn !== "/start") return next();
console.log("start: salom");
};
const aks = async (ctx) => {
console.log(`aks: ${ctx.matn}`);
};
const ishla = zanjir([log, start, aks]);
await ishla({ matn: "/start" });
await ishla({ matn: "non" });Konsolda:
> /start
start: salom
< /start
> non
aks: non
< nonKetma-ketlikka qarang:
/start—logkirdi,startmos keldi vanext()ni chaqirmadi.aksishlamadi. Keyin boshqaruvlogga qaytdi.non—startmos kelmadi,next()ni chaqirdi.aksishladi.
bot.command("start", f) aynan start ga o'xshaydi: mos kelsa — f, mos kelmasa — next().
3.2 Kanon tajribasi: haqiqiy grammY bilan
Kanon haqiqiy grammY 1.46.0 bilan shunday zanjir qurdi (soddalashtirilgan):
import { Bot, Composer } from "grammy";
const iz = [];
bot.use(async (ctx, next) => {
iz.push(`log>${ctx.update.update_id}`);
await next();
iz.push("log<ok");
});
const admin = new Composer();
admin.command("statistika", (ctx) => {
iz.push("admin:statistika");
return ctx.reply("faqat admin");
});
bot.filter((ctx) => ctx.from?.id === 1, admin);
bot.command("start", (ctx) => { iz.push("start"); });
bot.on("message:text", (ctx, next) => {
iz.push("text-1");
return next();
});
bot.on(":text", () => { iz.push("text-2"); });
bot.on("message:photo", (ctx) => {
iz.push(`photo ${ctx.message.photo.length} o'lcham`);
});Beshta update yuborildi. Natija (kanon o'lchovi, IlmHamroh, 2026-10, aynan). Har qatorda → belgilari update qaysi middleware'lardan o'tganini ko'rsatadi:
chat 1 "/statistika" → log>1001 → admin:statistika → log<ok
chat 2 "/statistika" → log>1002 → text-1 → text-2 (:text — message ham, channel_post ham) → log<ok
chat 2 "/start" → log>1003 → start → log<ok
chat 2 "oddiy matn" → log>1004 → text-1 → text-2 (:text — message ham, channel_post ham) → log<ok
rasm → log>1005 → photo 1 o'lcham → log<okHar qatorni o'qing:
- 1-qator. Foydalanuvchi 1 — admin.
bot.filtershartitrue, updateadminichiga kirdi va/statistikaishladi. - 2-qator. Foydalanuvchi 2 ham
/statistikayozdi. Filtr shartifalse— updateadminga umuman kirmadi va zanjirda pastga ketdi. Matn ishlovchilari uni oddiy matn deb oldi. Hujumchi "yashirin buyruq"ni bilsa ham, filtr uni o'tkazmaydi. - 3-qator.
/startbuyrug'istartda to'xtadi —next()yo'q. - 4-qator.
text-1next()ni chaqirdi, shuning uchuntext-2ham ishladi. Ikki ishlovchi bitta update'ni ko'rdi. - 5-qator. Rasm matn emas — faqat
message:photomos keldi.
Kanon chiqishidagi update_id lar — mock'ning hisoblagichi; haqiqiy Telegram'da ular boshqa son bo'ladi.
flowchart TD
U[Update] --> L[log middleware]
L --> F{from.id = 1?}
F -->|ha| A[admin Composer]
F -->|yo'q| S{/start?}
S -->|ha| ST[start]
S -->|yo'q| T{message:text?}
T -->|ha| T1[text-1 va next]
T1 --> T2[:text]
T -->|yo'q| P{message:photo?}
P -->|ha| PH[photo]Diagrammaga qarang: har romb — bitta middleware'ning "mosmi?" savoli. Mos kelmasa, update pastga tushadi. text-1 dan keyingi strelka — next() chaqiruvi.
3.3 Composer va bot.filter
Composer — middleware'lar to'plami, "kichik bot". Bot ning o'zi ham Composer dan meros oladi, shuning uchun unda ham .command, .on, .use bor. Katta botni modullarga bo'lishda qulay: admin.ts, tolov.ts — har biri o'z Composer ini eksport qiladi, asosiy faylda bot.use(admin).
bot.filter(shart, middleware) — shart true bo'lsa, update middleware'ga kiradi; aks holda zanjirda davom etadi. Shart odatda ruxsat bilan bog'liq: admin ro'yxati, faqat shaxsiy chat, faqat ma'lum guruh.
Maslahat: Admin tekshiruvini
ctx.from?.idbo'yicha qiling, foydalanuvchi nomi (username) bo'yicha emas.usernameni foydalanuvchi o'zgartira oladi va boshqa odam uni egallashi mumkin.ido'zgarmaydi.
Tekshirib ko'ring: Kanondagi 2-qatorda
/statistikanegabot.command("start")ga emas, matn ishlovchilariga tushdi?
Javob
bot.command("start") faqat /start buyrug'iga mos keladi. /statistika boshqa buyruq — u next() bilan pastga o'tdi. Unga mos buyruq ishlovchisi faqat admin ichida edi, filtr esa uni yopdi. Pastda faqat matn ishlovchilari qoldi: buyruq ham matn bo'lgani uchun ular uni oldi.
4. Filter query'lar
4.1 Sintaksis
bot.on(...) ga beriladigan satr — filter query (filtr so'rovi). U Update obyektining ichidagi yo'lni bildiradi:
| Query | Nimaga mos | Misol |
|---|---|---|
"message" |
har qanday yangi xabar | matn, rasm, stiker |
"message:text" |
matnli xabar | salom, /start |
"message:photo" |
rasmli xabar | foto |
":text" |
matnli xabar yoki kanal posti | message, channel_post |
"edited_message" |
tahrirlangan xabar | |
"callback_query:data" |
tugma bosildi | keyingi darsda |
Ikki nuqta oldidagi qism bo'sh bo'lsa (":text"), grammY uni bir nechta update turiga qisqartma deb oladi. Kanon chiqishidagi text-2 (:text — message ham, channel_post ham) aynan shuni eslatadi. Kanal postlari haqida Guruh, kanal botlari va inline rejim darsida gaplashamiz.
grammY hujjatida yana qisqartmalar bor: ":media" — rasm yoki video, ":file" — fayl biriktirilgan har qanday xabar (rasm, hujjat, ovoz, stiker va boshqalar).
4.2 Tiplar bilan bog'liqligi
grammY TypeScript'da yozilgan. bot.on("message:text", (ctx) => ...) ichida ctx.message.text ning tipi — string, undefined emas. Freymvork filtrdan bilib oladi: bu ishlovchiga faqat matnli xabar keladi. bot.on("message", ...) ichida esa ctx.message.text — string | undefined, chunki xabar rasm bo'lishi ham mumkin.
Bu Type guard g'oyasining freymvork ichidagi ko'rinishi: filtr — tekshiruv, tip esa tekshiruvdan keyin torayadi.
4.3 Buyruqlar
bot.command("start", f) — /start va /start parametr ga mos keladi. Guruhda Telegram buyruqni /start@sizning_bot shaklida yuborishi mumkin — grammY buni ham taniydi.
Buyruqlar ro'yxatini Telegram ilovasida menyu sifatida ko'rsatish uchun setMyCommands metodi bor (hujjatga ko'ra ko'pi bilan 100 ta buyruq):
await bot.api.setMyCommands([
{ command: "vazifalar", description: "Faol vazifalar" },
{ command: "yangi", description: "Yangi vazifa" },
{ command: "bekor", description: "Dialogni bekor qilish" },
]);Bu metod mock'da sinalmagan. Telegram hujjati alohida ogohlantiradi: menyuda ko'rinmagan buyruqni ham foydalanuvchi qo'lda yozishi mumkin. Shuning uchun har buyruqda huquqni serverda tekshiring.
5. Xatolar: bot.catch
5.1 Ushlanmagan xato botni to'xtatadi
Ishlovchi ichida xato chiqsa nima bo'ladi? Biz buni o'zimiz sinadik: /xato buyrug'i throw new Error(...) qiladi, bot.catch esa qo'yilmagan. Keyin foydalanuvchi oddiy salom yozdi:
bot.command("xato", () => {
throw new Error("ishlovchida xato");
});
bot.on("message:text", (ctx) => ctx.reply("javob"));
bot.start().catch((e) => {
console.log("start() rad etildi:", e.name, "-",
e.message.split("\n")[0]);
});Natija (biz ishga tushirdik, grammY 1.46.0, Node 24.21.0; xato izi at … qatorlari tashlab ketildi):
Error in middleware while handling update 1001 Error: ishlovchida xato
No error handler was set!
Set your own error handler with `bot.catch = ...`
Stopping bot
start() rad etildi: BotError - Error in middleware: ishlovchida xato
isRunning: false
oxirgi javob: (yo'q)grammY xabari: "Xato ishlovchisi o'rnatilmagan! O'zingizniki uchun bot.catch ni qo'ying". Keyin "Botni to'xtatyapman". bot.isRunning() — false, keyingi salom ga javob kelmadi. Bitta foydalanuvchining bitta xatosi — hamma uchun bot o'chdi.
5.2 bot.catch bilan
Endi o'sha botga bot.catch qo'shamiz:
import { Bot, GrammyError, HttpError } from "grammy";
bot.catch((err) => {
const e = err.error;
const tur = e instanceof GrammyError
? "GrammyError"
: e instanceof Error ? e.message : String(e);
const id = err.ctx.update.update_id;
console.log(`bot.catch: update ${id} — ${tur}`);
});Natija (biz ishga tushirdik — aynan shu formatlangan blok bilan, 2026-10-28):
bot.catch: update 1004 — ishlovchida xato
chat: 42 private | matn: bot tirikmi?
javob: Siz yozdingiz: bot tirikmi?Xato ushlandi, logga yozildi, bot ishlashda davom etdi.
err.error uch xil bo'lishi mumkin:
| Tur | Qachon | Misol |
|---|---|---|
GrammyError |
Telegram ok: false qaytardi |
400 noto'g'ri so'rov, 403 bloklangan |
HttpError |
Telegram'ga yetib bo'lmadi | tarmoq uzildi |
| boshqa | sizning kodingiz | TypeError, baza xatosi |
GrammyError da error_code va description bor — Bot prodakshnda darsida 403 va 429 ni alohida ishlaymiz.
Diqqat:
bot.catchfaqatbot.start()(long polling) uchun "oxirgi to'siq". Webhook rejimida xato veb freymvork'ingizga (Express) ko'tariladi. Ikkala holatda ham xatoni yashirmang — logga yozing (Logging asoslari).
6. Plagin tizimi
grammY yadrosi kichik. Qolgani plagin (plugin) — alohida npm paketlari. Ular ikki joyga ulanadi:
- Middleware plagin —
bot.use(plagin). Update'ni qayta ishlaydi:session(keyingi darslarda),conversations(ko'p qadamli dialog),menu(holatli tugmalar). - API transformer —
bot.api.config.use(plagin). Bot yuborayotgan so'rovni o'zgartiradi yoki takrorlaydi. Misol —autoRetry: Telegram 429 ("juda ko'p so'rov") qaytarsa, kutib, qayta yuboradi.
Kanon modulida ikkalasi ham bor:
bot.api.config.use(
autoRetry({ maxRetryAttempts: 3, maxDelaySeconds: 60 }),
);
bot.use(conversations<BotCtx, IchkiCtx>(
o.storage ? { storage: o.storage } : {},
));O'xshatish: middleware plagin — oshxonaga kiruvchi buyurtmani tekshiruvchi; API transformer — chiqib ketayotgan taomni stolga yetkazuvchi kuryer. Biri kirishda, biri chiqishda.
Kanon versiyalari: @grammyjs/auto-retry 2.0.2, @grammyjs/conversations 2.1.1, @grammyjs/menu 1.5.0, @grammyjs/runner 2.0.3.
7. grammY va Telegraf
Telegraf — grammY'dan oldin paydo bo'lgan Node bot freymvork. Kanon Telegraf'ni o'rnatmadi, shuning uchun taqqoslash faqat hujjat bo'yicha:
- npm'da Telegraf'ning eng oxirgi versiyasi — 4.16.3 (biz
npm view telegrafbilan tekshirdik, 2026-10-28). - grammY hujjatida taqqoslash sahifasi bor. U yozadi: grammY Telegraf'dan ilhomlangan; Telegraf 3 JavaScript'da edi, 4-versiyada TypeScript'ga ko'chdi, lekin tiplari murakkab bo'lib qoldi; grammY Bot API'ning yangi versiyasini tezroq qo'llab-quvvatlaydi.
- Bu sahifani grammY jamoasi yozgan — bir tomonlama manba. Xulosani o'zingiz qiling.
Asosiy g'oyalar ikkalasida bir xil: context, middleware, bot.command, bot.on. Telegraf'da sessiya va dialog — scenes va wizard; grammY'da — session va conversations plaginlari. Eski loyihalarda Telegraf kodini ko'p uchratasiz: ctx.reply, bot.launch() (grammY'da bot.start()).
| grammY | Telegraf | |
|---|---|---|
| Ishga tushirish | bot.start() |
bot.launch() |
| Matn filtri | bot.on("message:text") |
bot.on(message("text")) |
| Dialog | conversations plagini |
scenes, wizard |
| Kurs kanoni | 1.46.0, sinalgan | o'rnatilmagan |
Jadvaldagi Telegraf qatori — hujjat bo'yicha, biz ishga tushirmadik.
8. Hujumchi nigohi
Bot ham API — faqat interfeysi chat. Hujumchi nima qiladi?
| Hujum | Himoya |
|---|---|
admin buyrug'ini taxmin qilib yozadi (/statistika) |
bot.filter + ctx.from.id ro'yxati (kanon: 2-qator) |
username ni o'zgartirib admin bo'lib ko'rinadi |
id bo'yicha tekshirish |
/start ../../etc kabi soxta parametr |
parametrni regex bilan tekshirish (loyiha qadamida) |
| ishlovchini yiqitadigan xabar | bot.catch — bot to'xtamaydi |
| tokenni GitHub'dan topadi | token faqat muhit o'zgaruvchisida; oqsa — BotFather'da yangilash |
Token oqsa, hujumchi botingiz nomidan hamma foydalanuvchilarga yoza oladi. Bu eng og'ir holat.
9. Ko'p uchraydigan xatolar
| Xato | Natija | To'g'risi |
|---|---|---|
bot.catch yo'q |
bitta xato — bot to'xtaydi | bot.catch har doim |
| umumiy ishlovchi buyruqdan oldin | buyruqlar ishlamaydi | aniqlari yuqorida |
middleware'da await next() yo'q |
pastdagi ishlovchilar jim | o'tkazish kerak bo'lsa next() |
next() ni await siz chaqirish |
log "chiqish" vaqti noto'g'ri | await next() |
| tokenni kodga yozish | token oqadi | process.env.BOT_TOKEN |
bot.on("message") da ctx.message.text.trim() |
rasmda TypeError |
bot.on("message:text") |
10. Mashqlar
Mashqlar kurs/mashqlar/29/20-grammy/ da.
1-mashq (oson): qaysi ishlovchi?
Bot ishlovchilari shu tartibda: bot.use(log) (har doim next()), bot.command("yordam", A), bot.on("message:photo", B), bot.on("message:text", C) (next() siz), bot.on("message", D). Har update uchun qaysi harflar ishlaydi?
/yordamsalom- rasm
- stiker
Yechim
log,A— buyruq topildi,next()yo'q.log,C—Cnext()ni chaqirmaydi,Dishlamaydi.log,B—Bhamnext()siz.log,D— stiker na matn, na rasm; faqat umumiymessagemos.
2-mashq (o'rta): buyruq yordamchisi
Darsdagi zanjir uchun buyruq(nom, f) funksiyasini yozing. U middleware qaytaradi: ctx.matn /nom yoki /nom <matn> bo'lsa — ctx.match ga bo'sh joydan keyingi qismni yozib, f(ctx) ni chaqiradi; aks holda next(). /startup /start ga mos kelmasin. Ishora: startsWith (Matn ichida qidirish) va slice (Kesish, bo'lish va to'ldirish).
Yechim
function zanjir(ishlovchilar) {
return async (ctx) => {
const yurgiz = async (i) => {
const f = ishlovchilar[i];
if (f) await f(ctx, () => yurgiz(i + 1));
};
await yurgiz(0);
};
}
function buyruq(nom, f) {
const bosh = `/${nom}`;
return async (ctx, next) => {
const t = ctx.matn;
const mos = t === bosh || t.startsWith(`${bosh} `);
if (!mos) return next();
ctx.match = t.slice(bosh.length).trim();
await f(ctx);
};
}
const ishla = zanjir([
buyruq("start", (ctx) => console.log(`start: "${ctx.match}"`)),
async (ctx) => console.log(`boshqa: ${ctx.matn}`),
]);
await ishla({ matn: "/start" });
await ishla({ matn: "/start ulash-123" });
await ishla({ matn: "/startup" });
await ishla({ matn: "salom" });start: ""
start: "ulash-123"
boshqa: /startup
boshqa: salom/startup ham /start bilan boshlanadi. Shuning uchun faqat startsWith("/start") yetmaydi — bo'sh joy yoki to'liq tenglik shart.
3-mashq (qiyin): bot.catch ning nusxasi
zanjir ni o'rab turuvchi xatosiz(ishla, ushla) funksiyasini yozing: update'ni ishla ga beradi, xato bo'lsa ushla(xato, ctx) ni chaqiradi va xatoni tashqariga chiqarmaydi. Uch update yuboring: salom, /xato (ishlovchi throw qiladi), yana. Keyin xuddi shuni xatosiz siz try/catch bilan sikl qilib sinang: sikl birinchi xatoda to'xtashini ko'rsating. Ishora: try / catch / finally.
Yechim
function zanjir(ishlovchilar) {
return async (ctx) => {
const yurgiz = async (i) => {
const f = ishlovchilar[i];
if (f) await f(ctx, () => yurgiz(i + 1));
};
await yurgiz(0);
};
}
const ishla = zanjir([
async (ctx) => {
if (ctx.matn === "/xato") throw new Error("ishlovchida xato");
console.log(`javob: ${ctx.matn}`);
},
]);
function xatosiz(ishla, ushla) {
return async (ctx) => {
try {
await ishla(ctx);
} catch (e) {
ushla(e, ctx);
}
};
}
const xavfsiz = xatosiz(ishla, (e, ctx) => {
console.log(`ushlandi (${ctx.matn}): ${e.message}`);
});
for (const matn of ["salom", "/xato", "yana"]) {
await xavfsiz({ matn });
}
try {
for (const matn of ["salom", "/xato", "yana"]) {
await ishla({ matn });
}
} catch (e) {
console.log(`sikl to'xtadi: ${e.message}`);
}javob: salom
ushlandi (/xato): ishlovchida xato
javob: yana
javob: salom
sikl to'xtadi: ishlovchida xatoIkkinchi siklda yana ga javob yo'q — xuddi biz sinagan bot.catch siz bot kabi. bot.catch har update'ni alohida try/catch ga o'raydi.
11. Loyiha qadami: eslatma boti skeleti
Kanon vazifalar uchun Telegram eslatma botini modullar/src/telegram/bot.ts moduliga yozdi (grammY 1.46.0, TypeScript 6.0.3, Node 24.21.0). Bot vazifalar-api dan mustaqil: ma'lumotni VazifaManbai interfeysi orqali oladi. Testda — xotiradagi soxta manba, keyin — Postgres ombori. Bu test dublyori g'oyasi (Mock, stub, spy va fake).
Modul to'rt darsga bo'lingan: bugun — interfeys, /start (deep link) va /vazifalar; keyingi darsda — tugmalar; undan keyin — dialog; Bot prodakshnda darsida — autoRetry va bildirishnoma yuboruvchisi. Telefon uchun uzun qatorlarni bo'ldik va izohlarni qisqartirdik, kod ma'nosi kanondagidek. Bo'lingan nusxa ham tsc, eslint va testlardan o'tdi (biz tekshirdik).
Interfeys:
export type BotVazifa = {
id: number; matn: string; bajarildi: boolean; muddat: Date | null;
};
export interface VazifaManbai {
/** Deep link tokeni (bir martalik, qisqa muddatli) → foydalanuvchi
* id; ishlatilgach o'chadi */
tokenniIshlat(token: string): Promise<number | undefined>;
ulash(foydalanuvchiId: number, chatId: number): Promise<void>;
kim(chatId: number): Promise<number | undefined>;
royxat(foydalanuvchiId: number): Promise<BotVazifa[]>;
bajar(foydalanuvchiId: number, vazifaId: number): Promise<boolean>;
yarat(
foydalanuvchiId: number, matn: string, muddat: Date | null,
): Promise<{ id: number }>;
}11.1 Hisobni ulash: deep link
Muammo: botga Telegram foydalanuvchisi yozadi (chat 501), ilovada esa boshqa foydalanuvchi bor (id 1). Ularni qanday bog'laymiz? Foydalanuvchidan parol so'rash xato — chatga parol yozilmaydi.
Yechim — bir martalik token:
- Tizimga kirgan foydalanuvchi ilovada "Telegram'ni ulash" tugmasini bosadi.
- Server tasodifiy token yaratadi, uni foydalanuvchi id'si bilan qisqa muddatga saqlaydi.
- Ilova
https://t.me/sizning_bot?start=<token>havolasini ochadi. - Telegram botga
/start <token>yuboradi. - Bot tokenni bazadan topadi, o'chiradi va
chat_idni foydalanuvchiga bog'laydi.
sequenceDiagram
participant I as Ilova (brauzer)
participant S as Server
participant T as Telegram
participant B as Bot (grammY)
I->>S: "Telegram'ni ulash" (tizimga kirgan foydalanuvchi)
S-->>I: t.me/bot?start=TOKEN (token bazada, qisqa muddat)
I->>T: havolani ochadi, "Start"
T->>B: Update — /start TOKEN
B->>S: tokenniIshlat(TOKEN) — topadi va o'chiradi
S-->>B: foydalanuvchi id = 1
B->>S: ulash(1, chat_id)
B->>T: sendMessage "Hisob ulandi"Diagrammaga qarang: parol hech qayerda yo'q. Foydalanuvchini ilova taniydi (u tizimga kirgan), bot esa faqat bir martalik token orqali bog'lanadi.
bot.command("start", async (ctx) => {
const t = ctx.match.trim();
if (t === "") {
await ctx.reply(
"Salom! Bu — vazifalar eslatma boti.\nHisobni ulash uchun " +
"ilovadagi tugmani bosing, keyin: /vazifalar, /yangi",
);
return;
}
if (!/^[A-Za-z0-9_-]{16,64}$/.test(t)) {
await ctx.reply("Havola noto'g'ri.");
return;
}
const fId = await manba.tokenniIshlat(t);
if (fId === undefined) {
await ctx.reply(
"Havola eskirgan yoki ishlatilgan. Ilovadan yangisini oling.",
);
return;
}
await manba.ulash(fId, ctx.chat.id);
await ctx.reply(
"Hisob ulandi. Muddatlar haqida shu yerga yozaman. " +
"/vazifalar — ro'yxat",
);
});Regex Telegram deep link qoidasiga mos: faqat ruxsat etilgan belgilar, 64 tagacha. Pastki chegara 16 — qisqa tokenni taxmin qilish oson. Regex bazaga borishdan oldin ishlaydi: ../../etc kabi axlat bazaga yetmaydi.
Token bir martalik: kimdir havolani skrinshotda ko'rsa ham, u allaqachon ishlatilgan bo'ladi.
11.2 /vazifalar va bot.catch
bot.command("vazifalar", async (ctx) => {
const fId = await manba.kim(ctx.chat.id);
if (fId === undefined) {
await ctx.reply("Hisob ulanmagan. /start");
return;
}
const { matn, klaviatura } = royxatXabari(
await manba.royxat(fId),
);
await ctx.reply(matn, {
parse_mode: "HTML", reply_markup: klaviatura,
});
});
bot.catch((err) => {
const e = err.error;
const tur = e instanceof GrammyError
? `GrammyError ${String(e.error_code)} ${e.description}`
: e instanceof HttpError
? `HttpError ${String(e.error)}`
: String(e);
(o.log ?? console.error)({
update_id: err.ctx.update.update_id, xato: tur,
});
});royxatXabari — ro'yxat matni va tugmalar; uni keyingi darsda ochamiz. Har buyruq avval manba.kim(ctx.chat.id) bilan "bu chat kimniki?" deb so'raydi. Ulanmagan chat hech kimning vazifasini ko'rmaydi.
bot.catch xatoni strukturali obyekt sifatida logga beradi (o.log — test uchun almashtiriladi).
11.3 Testlar
Testlar modullar/test/telegram.test.ts da: haqiqiy grammY long polling bilan lokal soxta Bot API serveriga ulanadi. /start testidan parcha (formatlangan):
test("/start tokensiz, noto'g'ri, to'g'ri, takror (bir martalik)",
async () => {
let n = soni(501); tg.yozadi(501, "/start");
assert.match((await javob(501, n)).text ?? "", /Salom/);
n = soni(501); tg.yozadi(501, "/start ../../etc");
assert.equal((await javob(501, n)).text, "Havola noto'g'ri.");
n = soni(501);
tg.yozadi(501, "/start ulash-tokeni-aziz-1234567");
assert.match((await javob(501, n)).text ?? "", /Hisob ulandi/);
n = soni(502);
tg.yozadi(502, "/start ulash-tokeni-aziz-1234567");
assert.match((await javob(502, n)).text ?? "",
/eskirgan yoki ishlatilgan/);
assert.equal(await manba.kim(501), 1);
});To'rtinchi holat — muhim: boshqa chat (502) o'sha tokenni qayta ishlatdi va rad etildi.
Testlarni biz ishga tushirdik (kanon nusxasi, Node 24.21.0). Fayldagi testlar bir-biriga bog'liq (bitta bot holati), shuning uchun faylni butunligicha yurgizdik — ular keyingi darslar mavzusini ham qamraydi:
node --test test/telegram.test.ts✔ html va muddat yordamchilari (4.1698ms)
▶ vazifalar boti (soxta Bot API, long polling)
✔ /start tokensiz, noto'g'ri, to'g'ri, takror (bir martalik) (93.5114ms)
✔ /vazifalar: HTML ekranlangan, inline tugmalar; bosish → bajarildi, xabar tahrirlandi (62.1289ms)
✔ IDOR: begona vazifa id'si callback'da — 'topilmadi', Dilnozaniki o'zgarmaydi; eski tugma ham javob oladi (31.1902ms)
✔ /yangi dialog: noto'g'ri matn → qayta so'raydi, noto'g'ri muddat → qayta, yaratiladi; /bekor (138.3034ms)
✔ ulanmagan chat /yangi — dialog boshlanmaydi (15.1066ms)
✔ telegramYuboruvchi: yuborildi / 403 bloklangan → eskirgan / chat yo'q → eskirgan (11.0443ms)
✔ vazifalar boti (soxta Bot API, long polling) (398.0098ms)
▶ Bot API cheklovlari (to'g'ridan bot.api)
✔ callback_data 65 bayt va 4097 belgi — 400; HTML xatosi — 400 (7.7922ms)
✔ 429: auto-retry retry_after ni kutib takrorlaydi (chat: 3 portlash, 1/s) (3022.1695ms)
✔ Bot API cheklovlari (to'g'ridan bot.api) (3034.5212ms)
ℹ tests 9
ℹ suites 2
ℹ pass 9
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 3713.7387Millisekundlar har ishga tushishda boshqacha. 429 testi ~3 s davom etadi — u ataylab kutadi (Bot prodakshnda). Butun modullar paketi kanonda npm run check dan o'tgan: tsc 0, eslint 0, testlar 82/82.
Commit xabari (modul uchun):
git commit -m "feat(telegram): eslatma boti — deep link, /vazifalar"vazifalar-api ga ulash 28-qism (auth) tugagach bo'ladi: tokenni faqat tizimga kirgan foydalanuvchi oladi.
12. Real ishda
- O'zbekistonda Telegram — asosiy kanal: do'kon buyurtmalari, navbat, eslatmalar, ichki xabarnomalar ko'pincha bot orqali.
- Katta botlarda kod
Composermodullariga bo'linadi: har bo'lim (admin, to'lov, profil) o'z faylida. - Deep link bilan hisobni ulash — bank, do'kon va xizmat botlarida keng tarqalgan naqsh.
- Intervyuda: "Middleware zanjiri qanday ishlaydi?", "Bot ichida xatoni qanday ushlaysiz?", "Telegram hisobini ilova hisobiga qanday bog'laysiz?"
Xulosa
- grammY
getUpdates, offset va update turlarini o'zi boshqaradi; siz ishlovchi yozasiz. - Update middleware zanjiri bo'ylab o'tadi;
next()siz ishlovchi zanjirni to'xtatadi. - Filter query (
message:text,:text) update turini tanlaydi va tipni toraytiradi. Composer+bot.filter— ishlovchilar guruhini shart bilan yopish.bot.catchsiz bitta xato long polling botini to'xtatdi — uni har doim qo'ying.- Hisobni ulash — bir martalik deep link tokeni, tekshiruv bazadan oldin.
Keyingi dars: Klaviaturalar va callback query — tugmalar qo'shamiz, callback_data ning 64 baytli chegarasini o'lchaymiz va tugma orqali IDOR hujumini yopamiz.
Manbalar
- grammY hujjati: https://grammy.dev/guide/ (middleware, filter query'lar — https://grammy.dev/guide/filter-queries)
- grammY va Telegraf taqqoslashi (grammY jamoasi yozgan): https://grammy.dev/resources/comparison — 2026-10-28 da o'qildi
- Telegram deep linking: https://core.telegram.org/bots/features#deep-linking
setMyCommands: https://core.telegram.org/bots/api#setmycommands- Kanon tajribasi (IlmHamroh, 2026-10-27): grammy 1.46.0, @grammyjs/runner 2.0.3, @grammyjs/auto-retry 2.0.2, Node 24.21.0, lokal soxta Bot API serveri.
bot.catchtajribasi va modul testlari — biz ishga tushirdik (2026-10-28).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!