IlmHamroh
JavaScript Full-stack/29-qism. Backend integratsiyalar: fayl, to'lov, bot, realtime20/69-dars22 daqiqa
Mundarija (32)

grammY bilan bot yozish: context, middleware va filter query'lar

Qisqacha: grammY — Telegram Bot API ustidagi TypeScript freymvork (framework). U getUpdates siklini, 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.catch ni albatta qo'ying: biz sinaganimizda usiz bitta xato butun botni to'xtatdi.

Bu darsda

  • Bot, context (ctx) va ctx.reply bilan 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 va ctx.match ni ishlatasiz.
  • Composer va bot.filter bilan bir guruh ishlovchini ajratasiz.
  • Xatolarni bot.catch bilan ushlaysiz va plagin tizimini tushunasiz.
  • Loyiha uchun vazifalar eslatma 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:

  1. getUpdates ni timeout bilan chaqirasiz va offset ni to'g'ri oshirasiz.
  2. Update ichida nima borligini aniqlaysiz: message, callback_query, inline_query yoki boshqasi.
  3. Xabar matn bo'lsa — buyruqmi (/start) yoki oddiy matnmi, tekshirasiz.
  4. Javobni sendMessage bilan yuborasiz: chat_id ni o'zingiz topasiz.
  5. 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

bash
npm i grammy@1.46.0

Bot tokenini BotFather beradi (oldingi darsda ko'rgansiz). Token — parol: kodga yozilmaydi, muhit o'zgaruvchisidan olinadi.

js
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_id ni grammY o'zi qo'yadi.
  • bot.start() — long polling'ni boshlaydi: grammY getUpdates ni o'zi chaqiradi va offset ni 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:

text
match: ""
javob: Salom, Aziz!
match: "ulash-123"
chat: 42 private | matn: non olish
javob: Siz yozdingiz: non olish

Diqqat: 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", ...) ni bot.command("start", ...) dan oldin yozsak, /start ga 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:

js
// 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:

text
> /start
start: salom
< /start
> non
aks: non
< non

Ketma-ketlikka qarang:

  1. /start — log kirdi, start mos keldi va next() ni chaqirmadi. aks ishlamadi. Keyin boshqaruv log ga qaytdi.
  2. non — start mos kelmadi, next() ni chaqirdi. aks ishladi.

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):

js
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:

text
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<ok

Har qatorni o'qing:

  • 1-qator. Foydalanuvchi 1 — admin. bot.filter sharti true, update admin ichiga kirdi va /statistika ishladi.
  • 2-qator. Foydalanuvchi 2 ham /statistika yozdi. Filtr sharti false — update admin ga 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. /start buyrug'i start da to'xtadi — next() yo'q.
  • 4-qator. text-1 next() ni chaqirdi, shuning uchun text-2 ham ishladi. Ikki ishlovchi bitta update'ni ko'rdi.
  • 5-qator. Rasm matn emas — faqat message:photo mos 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?.id bo'yicha qiling, foydalanuvchi nomi (username) bo'yicha emas. username ni foydalanuvchi o'zgartira oladi va boshqa odam uni egallashi mumkin. id o'zgarmaydi.

Tekshirib ko'ring: Kanondagi 2-qatorda /statistika nega bot.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):

js
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:

js
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):

text
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:

js
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):

text
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.catch faqat bot.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:

  1. Middleware plagin — bot.use(plagin). Update'ni qayta ishlaydi: session (keyingi darslarda), conversations (ko'p qadamli dialog), menu (holatli tugmalar).
  2. 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:

ts
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 telegraf bilan 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?

  1. /yordam
  2. salom
  3. rasm
  4. stiker
Yechim
  1. log, A — buyruq topildi, next() yo'q.
  2. log, C — C next() ni chaqirmaydi, D ishlamaydi.
  3. log, B — B ham next() siz.
  4. log, D — stiker na matn, na rasm; faqat umumiy message mos.

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
js
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" });
text
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
js
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}`);
}
text
javob: salom
ushlandi (/xato): ishlovchida xato
javob: yana
javob: salom
sikl to'xtadi: ishlovchida xato

Ikkinchi 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:

ts
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 }>;
}

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:

  1. Tizimga kirgan foydalanuvchi ilovada "Telegram'ni ulash" tugmasini bosadi.
  2. Server tasodifiy token yaratadi, uni foydalanuvchi id'si bilan qisqa muddatga saqlaydi.
  3. Ilova https://t.me/sizning_bot?start=<token> havolasini ochadi.
  4. Telegram botga /start <token> yuboradi.
  5. Bot tokenni bazadan topadi, o'chiradi va chat_id ni 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.

ts
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

ts
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):

ts
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:

bash
node --test test/telegram.test.ts
text
✔ 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.7387

Millisekundlar 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):

bash
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 Composer modullariga 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.catch siz 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

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!