WisarWisar
Dasturlash kitobi/8-QISM — NestJS27 daqiqa

8.12-bob: NestJS Telegram bot (Telegraf) — barcha imkoniyatlar

8-QISM — NestJS (chuqur) · 12-mavzu


1. Kirish va motivatsiya

Test'ni 8.11-bob bildik. Endi eng amaliy, eng "ko'rinadigan" mavzulardan biriga — NestJS'da Telegram bot — o'tamiz. Bu bobda botning qila oladigan deyarli BARCHA imkoniyatini ko'rib chiqamiz: oddiy buyruqdan tortib inline tugmalar, scene/wizard (ko'p bosqichli dialog), fayl/media (rasm/video/ovoz/hujjat/stiker), joylashuv, kontakt, so'rovnoma (poll/quiz), inline rejim, Web App (mini-ilova), to'lov, xabarni tahrirlash/o'chirish/pin qilish, broadcast (ommaviy yuborish) gacha. 5.14'da botni Express'da o'rgandik; endi NestJS'da — nestjs-telegraf bilan, NestJS arxitekturasida (modul, DI, service, guard).

O'zbekistonda Telegram — eng asosiy platforma. Bot — biznesning bevosita mijoz bilan aloqasi: do'kon boti (katalog, buyurtma, to'lov), xizmat boti (navbat, ariza), admin boti (boshqaruv, statistika), bildirishnoma boti. Bot — sizning karyerangizda eng ko'p so'raladigan loyihalardan (O'zbekistonda har biznesga bot kerak). NestJS bilan bot — professional, kengaytiriladigan, test qilinadigan 8.11-bob.

nestjs-telegraf — Telegraf (eng kuchli Telegram bot kutubxonasi) ni NestJS'ga integratsiya qiladi: dekorator'lar (@Start, @Command, @Hears, @Action, @On), scene/wizard, DI (service inject), va NestJS guard/interceptor/filter/pipe to'liq qo'llab-quvvatlanadi. Bu — 5.14'ning NestJS versiyasi (tashkillangan, DI bilan). Bu bob: barcha update turlari, tugmalar, scene/wizard, media, to'lov, Web App, broadcast — eng to'liq. Bu — sizning eng ko'p quradigan loyihangiz.

O'xshatish: bot — 24/7 ishlaydigan avtomat xizmatchi (5.14: ofitsiant). Lekin bu bobda siz unga barcha ko'nikmalarni o'rgatasiz: gapni tushunish (buyruq/matn), tugma ko'rsatish (menyu), ko'p bosqichli suhbat (wizard — "ismingiz? telefoningiz? manzilingiz?"), rasm/video qabul qilish/yuborish, joylashuv so'rash, so'rovnoma o'tkazish, to'lov qabul qilish, hatto ichida mini-ilova (Web App) ochish. To'liq xizmatchi — mijozning har ehtiyojiga javob.

Nega muhim?

  • O'zbekistonda asosiy platforma — har biznesga bot kerak (eng ko'p loyiha).
  • Barcha imkoniyatlar — do'kon, xizmat, admin, to'lov, mini-ilova.
  • nestjs-telegraf — NestJS arxitekturasi (DI, modul, guard).
  • Professional — test qilinadigan, kengaytiriladigan bot.

2. Nazariya — chuqur tushuntirish

2.1. Bot asoslari (5.14 takrori — qisqa)

text
  Telegram Bot API 5.14-bob:
  - BotFather  bot yaratish  TOKEN
  - Bot foydalanuvchi xabariga JAVOB beradi (update)
  - 2 rejim: Polling (so'rab turish — dev) | Webhook (Telegram yuboradi — prod, 2.20)

  nestjs-telegraf — Telegraf'ni NestJS'ga (DI, dekorator, modul)

Bot — BotFather'dan token (5.14: 2.2). Foydalanuvchi xabari = update (bot javob beradi). Polling (dev) yoki Webhook (prod — 2.20). nestjs-telegraf — Telegraf'ni NestJS dekorator/DI'ga aylantiradi. Bu — 5.14 asosi (NestJS'da).

2.2. TelegrafModule sozlash

ts
// app.module.ts
import { TelegrafModule } from "nestjs-telegraf";

@Module({
  imports: [
    TelegrafModule.forRootAsync({                     // async (ConfigService — 8.3)
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        token: config.get("BOT_TOKEN"),               // .env (14)
        middlewares: [session()],                     // session (scene uchun — 2.10)
        include: [BotModule],                          // bot modullari
      }),
    }),
  ],
})
export class AppModule {}

TelegrafModuleforRootAsync (token env'dan — 14), middlewares (session — scene uchun — 2.10), include (bot modullari). NestJS DI bilan integratsiya. Bir nechta bot ham mumkin (botName bilan har biriga alohida token — multi-bot). Bu — botning markaziy sozlamasi.

2.3. Update turlari (barcha) — eng muhim jadval

text
  ┌─────────────────────────────────────────────────────────────┐
  │  TELEGRAM UPDATE TURLARI (bot nimani "eshitadi")              │
  ├─────────────────────────────────────────────────────────────┤
  │  @Start()              — /start (birinchi kirish)             │
  │  @Help()               — /help                                │
  │  @Command("buyurtma")  — /buyurtma (maxsus buyruq)            │
  │  @Hears("salom")       — aniq matn / regex                    │
  │  @On("text")           — har qanday matn                      │
  │  @On("photo")          — rasm yuborilganda                    │
  │  @On("video")          — video                                │
  │  @On("voice")          — ovozli xabar                         │
  │  @On("audio")          — audio fayl (musiqa)                  │
  │  @On("document")       — fayl/hujjat                          │
  │  @On("sticker")        — stiker                               │
  │  @On("animation")      — GIF/animatsiya                       │
  │  @On("location")       — joylashuv                            │
  │  @On("contact")        — kontakt (telefon)                    │
  │  @On("poll_answer")    — so'rovnoma javobi                    │
  │  @Action("tugma_id")   — inline tugma bosilganda (callback)   │
  │  @On("callback_query") — har qanday inline tugma              │
  │  @On("inline_query")   — inline rejim (@bot ... )             │
  │  @On("pre_checkout_query") — to'lovdan oldin 2.18-bob           │
  │  @On("successful_payment") — to'lov muvaffaqiyatli            │
  │  @On("new_chat_members")   — guruhga qo'shilish               │
  │  @On("edited_message")     — xabar tahrirlanganda             │
  └─────────────────────────────────────────────────────────────┘

Update turlari — bot "eshitadigan" hodisalar (eng muhim jadval). Dekorator'lar: @Start/@Command/@Hears (buyruq/matn), @On("photo/video/...") (media), @Action (inline tugma), @On("location/contact/poll_answer") (maxsus), to'lov 2.18-bob, inline 2.17-bob. Bu — botning barcha kirish nuqtalari.

2.4. Update (@Update) va Ctx

ts
import { Update, Start, Command, Ctx } from "nestjs-telegraf";
import { Context } from "telegraf";

@Update()                                            // controller'ga o'xshash (bot handler)
export class BotUpdate {
  constructor(private botService: BotService) {}     // DI (8.2)

  @Start()
  async start(@Ctx() ctx: Context) {                 // Ctx — kontekst (5.14: 2.5)
    await ctx.reply(`Salom, ${ctx.from.first_name}! Mana botga xush kelibsiz.`);
  }
}

@Update() — controller'ga o'xshash (bot handler klassi). Ctx (context) — xabar haqida hamma narsa (5.14: 2.5): ctx.from (kim), ctx.chat (qayer), ctx.message (xabar), ctx.reply() (javob). DI bilan service inject 8.2-bob. Update'lar — service'ni chaqiradi (controller kabi yupqa — 8.1: 2.7).

2.5. Javob yuborish metodlari (barcha)

ts
await ctx.reply("Oddiy matn");                        // matn
await ctx.replyWithHTML("<b>Qalin</b> <i>kursiv</i>");   // HTML format (2.6)
await ctx.replyWithMarkdownV2("*Qalin* _kursiv_");   // Markdown
await ctx.replyWithPhoto({ source: "rasm.jpg" });    // rasm (2.11)
await ctx.replyWithVideo({ url: "..." });            // video
await ctx.replyWithDocument({ source: "fayl.pdf" }); // hujjat
await ctx.replyWithLocation(41.31, 69.24);           // joylashuv (Toshkent)
await ctx.replyWithChatAction("typing");             // "yozmoqda..." holati
await ctx.editMessageText("Yangilangan");            // xabarni tahrirlash (2.16)
await ctx.deleteMessage();                            // o'chirish (2.16)
await ctx.answerCbQuery("Bajarildi!");               // inline tugmaga javob (2.9)

Javob metodlari (barcha): reply (matn), replyWithHTML/MarkdownV2 (format — 2.6), replyWithPhoto/Video/Document (media — 2.11), replyWithLocation (joylashuv), replyWithChatAction ("yozmoqda..."), editMessageText/deleteMessage (tahrir/o'chir — 2.16), answerCbQuery (inline javob — 2.9). Bu — botning barcha chiqish usullari.

2.6. Matn formatlash (HTML/Markdown)

ts
await ctx.replyWithHTML(`
<b>Qalin</b>, <i>kursiv</i>, <u>tagchiziq</u>, <s>chizilgan</s>
<code>kod</code>, <pre>blok kod</pre>
<a href="https://t.me">havola</a>
<tg-spoiler>yashirin</tg-spoiler>
`);

Formatlash: HTML (<b>, <i>, <code>, <a>, <tg-spoiler>) yoki MarkdownV2 (*, _, `). HTML — ishonchli (maxsus belgi muammosi kam). <code>/<pre> — kod ko'rsatish. Chiroyli, o'qiladigan xabar uchun muhim. MarkdownV2'da maxsus belgilar escape kerak.

2.7. Reply keyboard (oddiy tugmalar — 5.14: 2.8)

ts
import { Markup } from "telegraf";

await ctx.reply("Menyuni tanlang:", Markup.keyboard([
  [" Katalog", " Savatcha"],                     // qator 1
  [" Aloqa", " Ma'lumot"],                       // qator 2
  [Markup.button.contactRequest(" Telefon yuborish")],   // kontakt so'rash (2.13)
  [Markup.button.locationRequest(" Joylashuv")],   // joylashuv so'rash (2.14)
]).resize());                                         // ekranga moslash

Reply keyboard (5.14: 2.8) — klaviatura ustidagi tugmalar (matn yuboradi — @Hears bilan ushlanadi). Markup.keyboard([[...]]). Maxsus: contactRequest (telefon — 2.13), locationRequest (joylashuv — 2.14). .resize() (moslash), .oneTime() (bir marta). Asosiy menyu uchun.

2.8. Inline keyboard (xabar ichidagi tugmalar — 5.14: 2.9)

ts
await ctx.reply("Mahsulotni tanlang:", Markup.inlineKeyboard([
  [Markup.button.callback(" Sotib olish", "buy_1")],   // callback (@Action — 2.9)
  [Markup.button.url(" Sayt", "https://example.uz")],  // havola
  [Markup.button.webApp(" Do'kon", "https://shop.uz")], // Web App (2.19)
  [
    Markup.button.callback("", "minus_1"),
    Markup.button.callback("1 dona", "qty_1"),
    Markup.button.callback("", "plus_1"),
  ],
]));

Inline keyboard (5.14: 2.9) — xabar ichidagi tugmalar. Turlari: callback (bot ichida ishlov — @Action — 2.9), url (havola), webApp (mini-ilova — 2.19), switchToCurrentChat (inline — 2.17). callback data ("buy_1") — @Action bilan ushlanadi. Interaktiv UI uchun (savat, sahifalash).

2.9. Callback query (@Action — inline tugma ishlovi)

ts
@Action("buy_1")                                     // "buy_1" tugmasi bosilganda
async buy(@Ctx() ctx: Context) {
  await ctx.answerCbQuery("Savatga qo'shildi! ");   // tugmaga javob (yuqorida chiqadi)
  await ctx.editMessageText("Mahsulot savatga qo'shildi");   // xabarni yangilash (2.16)
}

// Dinamik callback (regex — ID bilan)
@Action(/buy_(\d+)/)                                 // buy_5, buy_12...
async buyDynamic(@Ctx() ctx: Context) {
  const id = ctx.match[1];                            // regex guruh (5.14)
  await ctx.answerCbQuery();
  await ctx.reply(`${id}-mahsulot tanlandi`);
}

@Action — inline tugma (callback) ishlovi. answerCbQuery()majburiy (tugmaning "yuklanish" holatini to'xtatadi; matn ko'rsatish ham mumkin). ctx.match — regex guruh (dinamik — buy_(\d+) ID). editMessageText — xabarni yangilash (yangi yubormasdan — 2.16). Bu — interaktiv tugmalarning yuragi.

2.10. Session (holat saqlash)

ts
// Session — foydalanuvchi holati (TelegrafModule middlewares: [session()])
interface SessionData { savat: number[]; bosqich?: string; }
interface MyContext extends Context { session: SessionData; }

@Hears(" Katalog")
async katalog(@Ctx() ctx: MyContext) {
  ctx.session.savat ??= [];                          // boshlash
  ctx.session.savat.push(1);                          // holatga qo'shish
  await ctx.reply(`Savatda ${ctx.session.savat.length} ta`);
}

Session — foydalanuvchi holatini saqlash (savat, dialog bosqichi). session() middleware 2.2-bob. ctx.session — har user uchun alohida. Default — xotirada (qayta ishga tushganda yo'qoladi); production'da Redis (8.15 — @telegraf/session/redis). Scene 2.11-bob session ustiga qurilgan.

2.11. Scene va Wizard (ko'p bosqichli dialog) — ENG MUHIM

text
  Scene — alohida "xona" (dialog konteksti):
  - Base Scene — erkin (enter/leave, handler'lar)
  - Wizard Scene — BOSQICHMA-BOSQICH (step 1  2  3)

  Wizard misol (ro'yxatdan o'tish):
  Step 0: "Ismingiz?"  matn  step++
  Step 1: "Telefoningiz?"  kontakt  step++
  Step 2: "Manzilingiz?"  joylashuv  saqla  leave

Scene — alohida dialog "xona"si (kontekst). Base (erkin) yoki Wizard (bosqichma-bosqich — eng ko'p ishlatiladi). Wizard — ko'p bosqichli forma (ism telefon manzil) uchun ideal. Session ustiga quriladi 2.10-bob. Bu — botning eng kuchli imkoniyati (murakkab dialog). 2.12'da kod.

2.12. Wizard Scene (kod — ro'yxatdan o'tish)

ts
import { Wizard, WizardStep, Ctx, Message } from "nestjs-telegraf";
import { Scenes } from "telegraf";

@Wizard("ro'yxat")                                   // scene nomi
export class RoyxatWizard {
  @WizardStep(1)
  async step1(@Ctx() ctx: Scenes.WizardContext) {
    await ctx.reply("Ismingizni kiriting:");
    ctx.wizard.next();                                // keyingi bosqich
  }

  @WizardStep(2)
  async step2(@Ctx() ctx: Scenes.WizardContext, @Message("text") ism: string) {
    ctx.wizard.state.ism = ism;                        // holatga saqlash
    await ctx.reply("Telefon raqamingizni yuboring:",
      Markup.keyboard([[Markup.button.contactRequest(" Yuborish")]]).resize());
    ctx.wizard.next();
  }

  @WizardStep(3)
  async step3(@Ctx() ctx: Scenes.WizardContext) {
    const contact = ctx.message?.["contact"];
    ctx.wizard.state.telefon = contact?.phone_number;
    await ctx.reply(`Rahmat! ${ctx.wizard.state.ism}, ro'yxatdan o'tdingiz.`);
    await ctx.scene.leave();                           // scene'dan chiqish
  }
}
// Boshlash: ctx.scene.enter("ro'yxat")

Wizard Scene@Wizard("nom") + @WizardStep(n) (bosqichlar). ctx.wizard.next() (keyingi), ctx.wizard.state (bosqichlar orasida saqlash), ctx.scene.enter("nom") (kirish), ctx.scene.leave() (chiqish). Ro'yxatdan o'tish, buyurtma, ariza uchun ideal. SceneModule'ga register qilinadi.

2.12b. Base Scene (erkin dialog "xona"si)

ts
import { Scene, SceneEnter, SceneLeave, On, Hears, Ctx } from "nestjs-telegraf";
import { Scenes } from "telegraf";

@Scene("qollab_quvvatlash")                          // scene nomi (@Wizard emas — erkin)
export class SupportScene {
  constructor(private botService: BotService) {}

  @SceneEnter()                                       // scene'ga kirilganda (bir marta)
  async enter(@Ctx() ctx: Scenes.SceneContext) {
    await ctx.reply("Savolingizni yozing. Chiqish uchun /bekor.");
  }

  @Hears("/bekor")                                    // scene ichidagi buyruq
  async bekor(@Ctx() ctx: Scenes.SceneContext) {
    await ctx.reply("Bekor qilindi.");
    await ctx.scene.leave();                           // scene'dan chiqish
  }

  @On("text")                                         // scene ichida har matn
  async savol(@Ctx() ctx: Scenes.SceneContext) {
    const matn = (ctx.message as any).text;
    await this.botService.savolYubor(ctx.from.id, matn);   // adminlarga (8.3)
    await ctx.reply("Rahmat! Savolingiz qabul qilindi, tez orada javob beramiz.");
    await ctx.scene.leave();
  }

  @SceneLeave()                                       // scene'dan chiqilganda
  async leave(@Ctx() ctx: Scenes.SceneContext) {
    await ctx.reply("Asosiy menyuga qaytdingiz.", this.asosiyMenyu());
  }

  private asosiyMenyu() {
    return Markup.keyboard([[" Katalog", " Aloqa"]]).resize();
  }
}
// Kirish: ctx.scene.enter("qollab_quvvatlash")

Base Scene — Wizard'dan farqli, bosqichsiz erkin dialog "xona"si. @Scene("nom") + @SceneEnter() (kirishda bir marta), @SceneLeave() (chiqishda), ichida odatiy @On/@Hears/@Action handler'lar ishlaydi (lekin faqat shu scene ichida). Qo'llab-quvvatlash, izoh qoldirish, erkin savol-javob kabi bosqichlar aniq bo'lmagan dialoglar uchun. Wizard 2.12-bob — qat'iy ketma-ketlik; Base — erkin. Ikkalasi ham session ustiga quriladi 2.10-bob va provider sifatida BotModule'ga qo'shiladi (Misol 4).

2.13. Kontakt qabul qilish (telefon)

ts
@On("contact")
async contact(@Ctx() ctx: Context) {
  const contact = ctx.message["contact"];
  const telefon = contact.phone_number;              // +998901234567
  const userId = contact.user_id;
  await this.botService.telefonSaqla(ctx.from.id, telefon);   // DB (8.3)
  await ctx.reply(`Rahmat! Telefon: ${telefon}`, Markup.removeKeyboard());
}

Kontakt@On("contact"). contactRequest tugmasi 2.7-bob bilan so'raladi foydalanuvchi telefonini yuboradi ctx.message.contact.phone_number. Ro'yxatdan o'tish, OTP'siz tasdiqlash 5.18-bob uchun. removeKeyboard (klaviaturani yashirish).

2.14. Joylashuv (location)

ts
@On("location")
async location(@Ctx() ctx: Context) {
  const { latitude, longitude } = ctx.message["location"];
  await this.botService.manzilSaqla(ctx.from.id, latitude, longitude);
  // Eng yaqin filialni topish (geo — 6.3 geospatial)
  const filial = await this.botService.engYaqinFilial(latitude, longitude);
  await ctx.reply(`Eng yaqin filial: ${filial.nom}`);
  await ctx.replyWithLocation(filial.lat, filial.lng);   // filial joylashuvi
}

Joylashuv@On("location"). locationRequest tugmasi 2.7-bob bilan ctx.message.location (lat/lng). Yetkazib berish manzili, eng yaqin filial (geo so'rov — 6.3), kuzatuv uchun. replyWithLocation (xarita yuborish). Live location ham mumkin.

2.15. Media qabul qilish va yuborish (rasm/video/ovoz/hujjat)

ts
// QABUL QILISH
@On("photo")
async photo(@Ctx() ctx: Context) {
  const photos = ctx.message["photo"];
  const fileId = photos[photos.length - 1].file_id;  // eng katta o'lcham
  const link = await ctx.telegram.getFileLink(fileId);   // yuklab olish havolasi
  await this.botService.rasmSaqla(ctx.from.id, link.href);   // S3 (5.11)
  await ctx.reply("Rasm qabul qilindi ");
}

@On("document")
async document(@Ctx() ctx: Context) {
  const doc = ctx.message["document"];
  await ctx.reply(`Fayl: ${doc.file_name} (${doc.file_size} bayt)`);
}

// YUBORISH (turli manbalar)
await ctx.replyWithPhoto({ source: "./rasm.jpg" });        // lokal fayl
await ctx.replyWithPhoto({ url: "https://.../rasm.jpg" }); // URL
await ctx.replyWithPhoto("AgACAgIAAxk...");                // file_id (tez — qayta yuborish)
await ctx.replyWithMediaGroup([                            // albom (bir nechta)
  { type: "photo", media: { url: "1.jpg" } },
  { type: "photo", media: { url: "2.jpg" } },
]);
await ctx.replyWithVideo({ source: "./tanish.mp4" }, { caption: "Video yo'riqnoma" });
await ctx.replyWithAudio({ source: "./musiqa.mp3" }, { title: "Reklama", performer: "Do'kon" });
await ctx.replyWithVoice({ source: "./ovoz.ogg" });        // ovozli xabar
await ctx.replyWithAnimation({ source: "./gif.mp4" });     // GIF/animatsiya
await ctx.replyWithSticker("CAACAgIAAxk...");             // stiker (file_id yoki source)

Media — qabul (@On("photo/video/voice/document/audio/sticker/animation")): file_id getFileLink (yuklab olish). Yuborish: har tur uchun alohida metod — replyWithPhoto/Video/Audio/Voice/Document/Sticker/Animation, replyWithMediaGroup (albom). Manba 3 xil: { source } (lokal fayl), { url } (URL), file_id (tez — qayta yuborish). file_id — Telegram'da saqlangan (qayta yuklamasdan). Bot media bilan to'liq ishlaydi.

2.16. Xabarni tahrirlash, o'chirish, pin

ts
// Tahrirlash (jonli yangilanish — sport, narx, savat)
const msg = await ctx.reply("Yuklanmoqda... 0%");
await ctx.telegram.editMessageText(ctx.chat.id, msg.message_id, undefined, "Tayyor! 100%");
await ctx.editMessageReplyMarkup(yangiTugmalar);     // faqat tugmalarni yangilash

// O'chirish
await ctx.deleteMessage();                            // joriy
await ctx.deleteMessage(msg.message_id);              // muayyan

// Pin / unpin
await ctx.pinChatMessage(msg.message_id);            // muhim e'lon
await ctx.unpinChatMessage(msg.message_id);

Tahrir/o'chir/pin: editMessageText/editMessageReplyMarkup (jonli yangilash — narx, savat, taymer — yangi xabar yubormasdan); deleteMessage (tozalash); pinChatMessage (muhim e'lon). Interaktiv UI (savat o'zgarishi) va toza chat uchun muhim.

2.17. Inline rejim (@bot ... — har chatda)

ts
@On("inline_query")
async inline(@Ctx() ctx: Context) {
  const query = ctx.inlineQuery.query;               // foydalanuvchi yozgani
  const mahsulotlar = await this.botService.qidir(query);   // DB qidiruv (8.3)
  const natija = mahsulotlar.map((m) => ({
    type: "article", id: String(m.id), title: m.nom,
    description: `${m.narx} so'm`,
    input_message_content: { message_text: `${m.nom}${m.narx} so'm` },
  }));
  await ctx.answerInlineQuery(natija);               // natijalarni ko'rsatish
}

Inline rejim@bot_username qidiruv (har qanday chatda, botga kirmasdan). @On("inline_query") qidiruv answerInlineQuery (natijalar). Foydalanuvchi natijani tanlab, istalgan chatga yuboradi. Qidiruv botlari (GIF, musiqa, mahsulot ulashish) uchun. BotFather'da yoqiladi.

2.18. So'rovnoma va quiz (poll)

ts
// Oddiy so'rovnoma
await ctx.replyWithPoll("Sevimli rang?", ["Qizil", "Yashil", "Ko'k"], {
  is_anonymous: false,
});

// Quiz (to'g'ri javobli)
await ctx.replyWithQuiz("2+2=?", ["3", "4", "5"], {
  correct_option_id: 1,                              // "4" to'g'ri
  explanation: "2+2=4",
});

@On("poll_answer")                                   // javobni ushlash
async pollAnswer(@Ctx() ctx: Context) {
  const answer = ctx.pollAnswer;
  await this.botService.javobSaqla(answer.user.id, answer.option_ids);
}

So'rovnoma: replyWithPoll (oddiy), replyWithQuiz (to'g'ri javobli — quiz). @On("poll_answer") — javobni ushlash (so'rov natijasi). Ovoz berish, viktorina, fikr so'rash uchun. Anonim/ochiq, ko'p tanlovli sozlamalar.

2.19. Web App (mini-ilova — eng zamonaviy)

ts
// Web App tugmasi (bot ichida to'liq veb-ilova ochiladi)
await ctx.reply("Do'konni oching:", Markup.inlineKeyboard([
  [Markup.button.webApp(" Do'kon", "https://shop.example.uz")],
]));

// Web App'dan kelgan ma'lumot (mini-ilovada buyurtma  botga)
@On("web_app_data")
async webAppData(@Ctx() ctx: Context) {
  const data = JSON.parse(ctx.message["web_app_data"].data);   // ilova yuborgan
  await this.botService.buyurtmaYarat(ctx.from.id, data);
  await ctx.reply(`Buyurtmangiz qabul qilindi! Summa: ${data.summa} so'm`);
}

Web App (Telegram Mini App) — bot ichida to'liq veb-ilova (React — 11). Markup.button.webApp(url) ilova ochiladi (telegram ichida). Ilova botga ma'lumot (web_app_data). To'liq do'kon, murakkab UI (oddiy tugmalar yetmaganda). Eng zamonaviy — Telegram'ning kuchli imkoniyati (O'zbekistonda ko'p ishlatiladi).

2.20. To'lov (Telegram Payments)

ts
// Invoys (hisob-faktura) yuborish
await ctx.replyWithInvoice({
  title: "Premium obuna",
  description: "1 oylik premium",
  payload: "premium_1month",                         // ichki ID
  provider_token: this.config.get("PAYMENT_TOKEN"),  // BotFather  provayder (Click/Payme)
  currency: "UZS",
  prices: [{ label: "Premium", amount: 50000 * 100 }],   // tiyin (×100)
});

@On("pre_checkout_query")                            // to'lovdan oldin tasdiqlash
async preCheckout(@Ctx() ctx: Context) {
  await ctx.answerPreCheckoutQuery(true);            // tasdiqlash (10 soniya ichida!)
}

@On("successful_payment")                            // to'lov muvaffaqiyatli
async paid(@Ctx() ctx: Context) {
  const payment = ctx.message["successful_payment"];
  await this.botService.obunaYoq(ctx.from.id, payment.invoice_payload);
  await ctx.reply("To'lov muvaffaqiyatli! Premium yoqildi ");
}

To'lovreplyWithInvoice (hisob — provayder token BotFather'dan: Click/Payme/Stripe). Oqim: invoys pre_checkout_query (tasdiqlash — 10 soniya ichida javob!) successful_payment (yakunlandi). UZS — tiyin (×100). To'g'ridan-to'g'ri bot ichida to'lov (eng qulay). O'zbekistonda Click/Payme integratsiyasi.

2.21. Guruh va admin imkoniyatlari

ts
@On("new_chat_members")                              // guruhga qo'shilish
async welcome(@Ctx() ctx: Context) {
  const yangi = ctx.message["new_chat_members"];
  for (const a of yangi) await ctx.reply(`Xush kelibsiz, ${a.first_name}!`);
}

// Admin amallari (guruhda)
await ctx.banChatMember(userId);                     // ban
await ctx.restrictChatMember(userId, { permissions: {...} });   // cheklash
await ctx.deleteMessage();                            // spam o'chirish
await ctx.promoteChatMember(userId, { can_delete_messages: true });

Guruh/admin: @On("new_chat_members") (xush kelibsiz), banChatMember (ban), restrictChatMember (cheklash — mute), deleteMessage (spam), promoteChatMember (admin qilish). Guruh boshqaruv botlari (moderatsiya, anti-spam) uchun. Bot guruhda admin bo'lishi kerak.

2.22. Broadcast (ommaviy yuborish)

ts
// Barcha foydalanuvchilarga e'lon (navbat orqali — 8.22, rate limit!)
async broadcast(xabar: string) {
  const userlar = await this.usersService.barchaTelegramId();   // DB (8.3)
  for (const userId of userlar) {
    await this.botQueue.add("broadcast", { userId, xabar });   // navbatga (8.22)
  }
}
// Worker 8.22-bob — sekin yuboradi (Telegram limit: ~30 msg/sek)
//  Bloklagan/o'chirgan user  xato (try/catch, DB'dan belgilash)

Broadcast — barcha user'ga e'lon. Rate limit (Telegram ~30 xabar/sek) navbat orqali (BullMQ — 8.22, sekin). Bloklagan user xato (try/catch — DB'da "bloklagan" belgilash). Marketing, e'lon uchun. To'g'ridan-to'g'ri sikl — ban xavfi (rate limit buziladi).

2.23. NestJS guard/filter bot'da (8.6)

ts
// Bot uchun guard (admin tekshiruvi)
@Injectable()
export class AdminGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const ctx = TelegrafExecutionContext.create(context).getContext<Context>();
    return ADMIN_IDS.includes(ctx.from.id);          // admin ID
  }
}

@Command("admin")
@UseGuards(AdminGuard)                                // faqat admin (8.6)
async admin(@Ctx() ctx: Context) {
  await ctx.reply("Admin paneli");
}

Guard/filter bot'da — nestjs-telegraf NestJS guard/interceptor/filter/pipe to'liq qo'llaydi. TelegrafExecutionContext — bot ctx'ini olish (HTTP Request o'rniga). AdminGuard (admin tekshiruvi), exception filter (xato ushlash — 2.24). Bu — nestjs-telegraf'ning kuchi (8.6 bot'da). Throttler (spam — 8.16).

Telegraf middleware (har update oldidan): guard bir handler'ni himoya qilsa, middleware har update oqimiga aralashadi (logging, foydalanuvchini DB'dan yuklash, tilni aniqlash):

ts
// Har update uchun oraliq qatlam (session'dan keyin, handler'dan oldin)
TelegrafModule.forRootAsync({
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    token: config.get("BOT_TOKEN"),
    middlewares: [
      session(),                                       // avval session (2.10)
      async (ctx, next) => {                           // keyin custom middleware
        const boshlanish = Date.now();
        await next();                                   // handler'ni chaqirish (majburiy!)
        console.log(`${ctx.updateType}${Date.now() - boshlanish}ms`);   // log (5.12)
      },
    ],
  }),
}),

Middlewarenext() majburiy chaqiriladi (aks holda handler ishlamaydi). Tartib muhim: session() custom middleware'dan oldin turishi kerak (ctx.session tayyor bo'lishi uchun). Foydalanuvchini bir marta DB'dan yuklab ctx.state'ga qo'yish (har handler'da qayta so'ramaslik) uchun ideal.

2.24. Xatolarni boshqarish va Webhook (prod)

ts
// Global bot xato filtri (8.6)
@Catch()
export class TelegrafExceptionFilter implements ExceptionFilter {
  async catch(exception: any, host: ArgumentsHost) {
    const ctx = TelegrafArgumentsHost.create(host).getContext<Context>();
    await ctx.reply("Xatolik yuz berdi. Keyinroq urinib ko'ring.");
    console.error(exception);                          // log (5.12)
  }
}

// Global Telegraf darajasidagi ushlagich — bot.catch (@InjectBot bilan)
@Injectable()
export class BotErrorSetup implements OnModuleInit {
  constructor(@InjectBot() private bot: Telegraf<Context>) {}
  onModuleInit() {
    this.bot.catch(async (err, ctx) => {               // eng past darajadagi "himoya to'ri"
      console.error(`Bot xatosi (update ${ctx.updateType}):`, err);
      try {
        await ctx.reply(" Kutilmagan xatolik. Keyinroq urinib ko'ring.");
      } catch {}
    });
  }
}

// Webhook (production — 2.1)
TelegrafModule.forRoot({
  token: BOT_TOKEN,
  launchOptions: { webhook: { domain: "https://bot.example.uz", path: "/secret-path" } },
});

// Polling (dev — sozlamalar bilan)
TelegrafModule.forRoot({
  token: BOT_TOKEN,
  launchOptions: { dropPendingUpdates: true },         // ishga tushganda eski update'larni tashlab yuborish
});

Xato filtri — bot xatosi user'ni "yiqitmasligi" uchun (8.6 — @Catch, NestJS uslubi). bot.catch — Telegraf'ning o'z global ushlagichi (@InjectBot orqali sozlanadi): NestJS filtri ushlamay qolgan har qanday xatoning oxirgi "himoya to'ri". Odatda @Catch filtri yetarli; bot.catch — qo'shimcha kafolat. Webhook vs Polling: polling — bot Telegram'dan update'ni so'rab turadi (dev — oddiy, port ochish shart emas); webhook — Telegram update'ni serverga o'zi yuboradi (prod — tezroq, resurs tejaydi). Webhook uchun HTTPS domain + maxfiy path kerak. dropPendingUpdates — restart'da to'planib qolgan eski xabarlarni tashlab yuboradi.

2.25. Best practices (bot)

text
   Token .env (forRootAsync — 14, 2.2)
   Update yupqa  service (controller kabi — 8.1: 2.7, 2.4)
   Session production'da Redis (8.15, 2.10)
   Wizard — murakkab dialog 2.12-bob; answerCbQuery majburiy 2.9-bob
   file_id qayta ishlatish (tez — 2.15)
   Broadcast navbat + rate limit (8.22, 2.22)
   Guard (admin), exception filter (8.6, 2.23, 2.24)
   Webhook production 2.24-bob; xatoni ushlash
   Foydalanuvchi tilini saqlash (i18n — ko'p til)
   DB'da foydalanuvchi (start'da ro'yxat — 8.3)

3. Sintaksis — tez ma'lumotnoma

ts
// Handler'lar (2.3)
@Update() class BotUpdate {}
@Start()  @Help()  @Command("x")  @Hears("matn"/regex)
@On("text"|"photo"|"location"|"contact"|"callback_query"|...)
@Action("data"/regex)   // inline tugma

// Javob 2.5-bob: ctx.reply(), replyWithPhoto/Video/Location, editMessageText, answerCbQuery
// Tugma (2.7, 2.8): Markup.keyboard([[...]]) | Markup.inlineKeyboard([[Markup.button.callback/url/webApp]])
// Scene 2.12-bob: @Wizard("nom") @WizardStep(n) | @Scene("nom") @SceneEnter/@SceneLeave; ctx.scene.enter/leave, ctx.wizard.next/state
// To'lov 2.20-bob: replyWithInvoice; @On("pre_checkout_query"/"successful_payment")

4. Batafsil kod namunalari

Misol 1 — Bot modul va Update (asos — 2.2, 2.4)

ts
// bot.module.ts
@Module({
  imports: [UsersModule],
  providers: [BotUpdate, BotService, RoyxatWizard, BuyurtmaWizard],
})
export class BotModule {}

// bot.update.ts
@Update()
export class BotUpdate {
  constructor(private botService: BotService) {}     // DI (8.2)

  @Start()
  async start(@Ctx() ctx: Context) {
    await this.botService.foydalanuvchiRoyxat(ctx.from);   // DB'ga (8.3)
    await ctx.replyWithHTML(
      `Assalomu alaykum, <b>${ctx.from.first_name}</b>!\nMana do'konga xush kelibsiz `,
      this.asosiyMenyu(),
    );
  }

  @Help()
  async help(@Ctx() ctx: Context) {
    await ctx.reply("Buyruqlar:\n/start — boshlash\n/katalog — mahsulotlar\n/savat — savatcha");
  }

  private asosiyMenyu() {
    return Markup.keyboard([
      [" Katalog", " Savat"],
      [" Aloqa", " Profil"],
    ]).resize();
  }
}

Misol 2 — Inline katalog + sahifalash (2.8, 2.9)

ts
@Hears(" Katalog")
async katalog(@Ctx() ctx: Context) {
  await this.sahifaKorsat(ctx, 0);
}

private async sahifaKorsat(ctx: Context, sahifa: number) {
  const { mahsulotlar, jami } = await this.botService.mahsulotlar(sahifa, 5);
  const tugmalar = mahsulotlar.map((m) => [
    Markup.button.callback(`${m.nom}${m.narx} so'm`, `product_${m.id}`),
  ]);
  // Sahifalash tugmalari
  const nav = [];
  if (sahifa > 0) nav.push(Markup.button.callback("", `page_${sahifa - 1}`));
  if ((sahifa + 1) * 5 < jami) nav.push(Markup.button.callback("", `page_${sahifa + 1}`));
  if (nav.length) tugmalar.push(nav);
  await ctx.reply("Mahsulotlar:", Markup.inlineKeyboard(tugmalar));
}

@Action(/page_(\d+)/)                                // sahifa o'tish
async sahifa(@Ctx() ctx: Context) {
  await ctx.answerCbQuery();
  await ctx.deleteMessage();
  await this.sahifaKorsat(ctx, Number(ctx.match[1]));
}

@Action(/product_(\d+)/)                             // mahsulot tafsiloti
async product(@Ctx() ctx: Context) {
  const id = Number(ctx.match[1]);
  const m = await this.botService.mahsulot(id);
  await ctx.answerCbQuery();
  await ctx.replyWithPhoto(m.rasm, {
    caption: `<b>${m.nom}</b>\n${m.tavsif}\n ${m.narx} so'm`,
    parse_mode: "HTML",
    ...Markup.inlineKeyboard([
      [Markup.button.callback(" Savatga", `add_${id}`)],
    ]),
  });
}

Misol 3 — Wizard: buyurtma berish (ko'p bosqich — 2.12)

ts
@Wizard("buyurtma")
export class BuyurtmaWizard {
  constructor(private botService: BotService) {}

  @WizardStep(1)
  async ism(@Ctx() ctx: Scenes.WizardContext) {
    await ctx.reply("Ismingizni kiriting:");
    ctx.wizard.next();
  }

  @WizardStep(2)
  async telefon(@Ctx() ctx: Scenes.WizardContext) {
    const text = (ctx.message as any)?.text;
    if (!text || text.length < 2) { await ctx.reply("Ismni to'g'ri kiriting:"); return; }   // validatsiya
    ctx.wizard.state["ism"] = text;
    await ctx.reply(" Telefon raqamingizni yuboring:",
      Markup.keyboard([[Markup.button.contactRequest("Yuborish")]]).resize());
    ctx.wizard.next();
  }

  @WizardStep(3)
  async manzil(@Ctx() ctx: Scenes.WizardContext) {
    const contact = (ctx.message as any)?.contact;
    if (!contact) { await ctx.reply("Tugma orqali yuboring:"); return; }
    ctx.wizard.state["telefon"] = contact.phone_number;
    await ctx.reply(" Yetkazish manzilini yuboring:",
      Markup.keyboard([[Markup.button.locationRequest("Joylashuv")]]).resize());
    ctx.wizard.next();
  }

  @WizardStep(4)
  async tasdiq(@Ctx() ctx: Scenes.WizardContext) {
    const loc = (ctx.message as any)?.location;
    const state = ctx.wizard.state as any;
    await this.botService.buyurtmaYakunla(ctx.from.id, {
      ism: state.ism, telefon: state.telefon, lat: loc?.latitude, lng: loc?.longitude,
    });
    await ctx.reply(` Buyurtma qabul qilindi!\n ${state.ism}\n ${state.telefon}`,
      Markup.removeKeyboard());
    await ctx.scene.leave();
  }
}

// Boshlash (inline yoki buyruq)
@Action("checkout")
async checkout(@Ctx() ctx: Scenes.SceneContext) {
  await ctx.answerCbQuery();
  await ctx.scene.enter("buyurtma");                 // wizard'ga kirish
}

Misol 4 — Scene registratsiya (modul — 2.11)

ts
// app.module.ts
TelegrafModule.forRootAsync({
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    token: config.get("BOT_TOKEN"),
    middlewares: [session()],                        // scene uchun majburiy (2.10)
  }),
}),

// bot.module.ts — wizard'lar provider sifatida
@Module({
  providers: [BotUpdate, BotService, BuyurtmaWizard, RoyxatWizard],
})
export class BotModule {}
// nestjs-telegraf @Wizard'larni avtomatik scene sifatida ro'yxatga oladi

Misol 5 — Media: rasm qabul + S3 (2.15, 5.11)

ts
@On("photo")
async photo(@Ctx() ctx: Context) {
  await ctx.replyWithChatAction("upload_photo");     // "rasm yuklanmoqda..."
  const photos = (ctx.message as any).photo;
  const fileId = photos[photos.length - 1].file_id;  // eng yuqori sifat
  const link = await ctx.telegram.getFileLink(fileId);

  const url = await this.botService.rasmYukla(ctx.from.id, link.href);   // S3 (5.11)
  await ctx.reply(" Rasm saqlandi");
}

// Yuborish (file_id keshlash — tez)
@Command("logo")
async logo(@Ctx() ctx: Context) {
  const saqlangan = await this.botService.logoFileId();   // DB'da file_id
  if (saqlangan) {
    await ctx.replyWithPhoto(saqlangan);             // file_id (qayta yuklamasdan — tez!)
  } else {
    const msg = await ctx.replyWithPhoto({ source: "./logo.png" });
    const fileId = msg.photo[msg.photo.length - 1].file_id;
    await this.botService.logoFileIdSaqla(fileId);   // keyingisi uchun
  }
}

Misol 6 — To'lov oqimi (Click/Payme — 2.20)

ts
@Action("buy_premium")
async invoice(@Ctx() ctx: Context) {
  await ctx.answerCbQuery();
  await ctx.replyWithInvoice({
    title: "Premium obuna (1 oy)",
    description: "Cheksiz buyurtma, tezkor yetkazish, chegirmalar",
    payload: `premium_${ctx.from.id}_${Date.now()}`,
    provider_token: this.config.get("PAYMENT_TOKEN"),   // Click/Payme (BotFather)
    currency: "UZS",
    prices: [{ label: "Premium 1 oy", amount: 50000 * 100 }],   // tiyin
  });
}

@On("pre_checkout_query")
async preCheckout(@Ctx() ctx: Context) {
  // Bu yerda zaxira/narx qayta tekshiriladi
  await ctx.answerPreCheckoutQuery(true);            // 10 soniya ichida! (2.20)
}

@On("successful_payment")
async success(@Ctx() ctx: Context) {
  const p = (ctx.message as any).successful_payment;
  await this.botService.premiumYoq(ctx.from.id, 30); // 30 kun
  await ctx.replyWithHTML(` <b>To'lov muvaffaqiyatli!</b>\nPremium 30 kunga yoqildi.`);
}

Misol 7 — So'rovnoma va Web App (2.18, 2.19)

ts
// So'rovnoma
@Command("sorov")
async sorov(@Ctx() ctx: Context) {
  await ctx.replyWithPoll("Xizmatimizni baholang:", ["", "", ""], {
    is_anonymous: false,
  });
}

@On("poll_answer")
async pollResult(@Ctx() ctx: Context) {
  const a = ctx.pollAnswer;
  await this.botService.bahoSaqla(a.user.id, a.option_ids[0]);
}

// Web App (mini-ilova)
@Command("dokon")
async dokon(@Ctx() ctx: Context) {
  await ctx.reply("To'liq do'konni oching:", Markup.inlineKeyboard([
    [Markup.button.webApp(" Do'kon ilovasi", "https://shop.example.uz/tg")],
  ]));
}

@On("web_app_data")
async webApp(@Ctx() ctx: Context) {
  const data = JSON.parse((ctx.message as any).web_app_data.data);
  const buyurtma = await this.botService.webBuyurtma(ctx.from.id, data);
  await ctx.reply(` Buyurtma #${buyurtma.id}\n ${buyurtma.summa} so'm`);
}

Misol 8 — Admin guard va panel (2.23)

ts
// admin.guard.ts
@Injectable()
export class AdminGuard implements CanActivate {
  constructor(private config: ConfigService) {}
  canActivate(context: ExecutionContext): boolean {
    const ctx = TelegrafExecutionContext.create(context).getContext<Context>();
    const adminlar = this.config.get("ADMIN_IDS").split(",").map(Number);
    return adminlar.includes(ctx.from.id);
  }
}

// Admin buyruqlari
@Command("admin")
@UseGuards(AdminGuard)
async adminPanel(@Ctx() ctx: Context) {
  const stat = await this.botService.statistika();
  await ctx.replyWithHTML(
    ` <b>Statistika</b>\n Foydalanuvchi: ${stat.userlar}\n Buyurtma: ${stat.buyurtmalar}`,
    Markup.inlineKeyboard([
      [Markup.button.callback(" E'lon yuborish", "broadcast_start")],
      [Markup.button.callback(" Buyurtmalar", "orders_list")],
    ]),
  );
}

Misol 9 — Broadcast (navbat orqali — 2.22, 8.22)

ts
// Broadcast service (rate limit himoyasi)
@Injectable()
export class BotService {
  constructor(@InjectQueue("broadcast") private queue: Queue) {}

  async broadcastBoshla(xabar: string) {
    const userlar = await this.usersService.barchaTelegramId();
    for (const userId of userlar) {
      await this.queue.add("send", { userId, xabar }, {
        delay: 0, attempts: 2,                       // qayta urinish (8.22)
      });
    }
    return userlar.length;
  }
}

// broadcast.processor.ts (worker — 8.22)
@Processor("broadcast")
export class BroadcastProcessor extends WorkerHost {
  constructor(@InjectBot() private bot: Telegraf) { super(); }

  async process(job: Job) {
    try {
      await this.bot.telegram.sendMessage(job.data.userId, job.data.xabar);
    } catch (e) {
      if (e.code === 403) {                          // user botni bloklagan
        await this.usersService.bloklaganBelgila(job.data.userId);
      }
    }
  }
}
//  Rate limit: worker concurrency cheklangan (~25/sek — Telegram limit)

Misol 10 — Exception filter va xato boshqaruv (2.24)

ts
// telegraf-exception.filter.ts
@Catch()
export class TelegrafExceptionFilter implements ExceptionFilter {
  private logger = new Logger("Bot");
  async catch(exception: any, host: ArgumentsHost) {
    const telegrafHost = TelegrafArgumentsHost.create(host);
    const ctx = telegrafHost.getContext<Context>();
    this.logger.error(exception.message, exception.stack);   // log (5.12)
    try {
      await ctx.reply(" Xatolik yuz berdi. Iltimos, qaytadan urinib ko'ring.");
    } catch {}                                        // reply ham xato bo'lishi mumkin
  }
}

// Global (bot.module.ts)
{ provide: APP_FILTER, useClass: TelegrafExceptionFilter }

Misol 11 — Foydalanuvchi tili (i18n — ko'p til)

ts
@Action(/lang_(uz|ru|en)/)
async til(@Ctx() ctx: Context) {
  const til = ctx.match[1];
  await this.botService.tilSaqla(ctx.from.id, til);  // DB (8.3)
  await ctx.answerCbQuery();
  const matn = { uz: "Til o'zgartirildi", ru: "Til o'zgartirildi", en: "Language changed" };
  await ctx.reply(matn[til]);
}
// Til tanlash menyusi
@Command("til")
async tanla(@Ctx() ctx: Context) {
  await ctx.reply("Tilni tanlang:", Markup.inlineKeyboard([
    [Markup.button.callback(" O'zbek", "lang_uz")],
    [Markup.button.callback(" Rus", "lang_ru")],
    [Markup.button.callback(" English", "lang_en")],
  ]));
}

Misol 12 — Jonli yangilanish (taymer/savat — 2.16)

ts
// Savat miqdorini jonli yangilash (yangi xabarsiz)
@Action(/qty_(plus|minus)_(\d+)/)
async miqdor(@Ctx() ctx: Context) {
  const [, amal, id] = ctx.match;
  const yangiMiqdor = await this.botService.miqdorOzgartir(
    ctx.from.id, Number(id), amal === "plus" ? 1 : -1,
  );
  await ctx.answerCbQuery(`Miqdor: ${yangiMiqdor}`);
  // Faqat tugmalarni yangilash (xabar o'zi qoladi — 2.16)
  await ctx.editMessageReplyMarkup({
    inline_keyboard: [[
      { text: "", callback_data: `qty_minus_${id}` },
      { text: `${yangiMiqdor} dona`, callback_data: "noop" },
      { text: "", callback_data: `qty_plus_${id}` },
    ]],
  });
}

5. To'g'ri va noto'g'ri holatlar

1) Token hardcode

text
 kodda token (14)
 .env (forRootAsync)

2) answerCbQuery'siz inline tugma

text
 tugma "yuklanmoqda" qotib qoladi (2.9)
 ctx.answerCbQuery() har @Action'da

3) Broadcast to'g'ridan-to'g'ri siklda

text
 for  sendMessage (rate limit — ban — 2.22)
 navbat (BullMQ) + concurrency limit

4) Session xotirada (production)

text
 default session (restart  yo'qoladi — 2.10)
 Redis session (8.15)

5) Update'da og'ir mantiq

text
 DB/biznes mantiq update'da (8.1: 2.7)
 service'da (update yupqa)

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — Bot javob bermaydi

Sababi: token xato, yoki include/provider yo'q 2.2-bob. Yechimi: token .env; BotModule include; provider ro'yxat.

Xato 2 — Scene ishlamaydi (Cannot read scene)

Sababi: session middleware yo'q 2.10-bob. Yechimi: middlewares: [session()]; wizard provider.

Xato 3 — Inline tugma javob bermaydi

Sababi: @Action callback data mos emas 2.9-bob. Yechimi: callback data va @Action mos; regex to'g'ri.

Xato 4 — pre_checkout_query timeout (to'lov)

Sababi: 10 soniyada javob berilmadi 2.20-bob. Yechimi: answerPreCheckoutQuery tez; og'ir ish keyin.

Xato 5 — Broadcast'da bot bloklandi (429)

Sababi: rate limit 2.22-bob. Yechimi: navbat + concurrency ~25/sek; retry.

Xato 6 — file_id boshqa botda ishlamaydi

Sababi: file_id botga bog'liq. Yechimi: har bot o'z file_id; yoki URL/source.


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • Telegram bot 5.14-bob: Telegraf — NestJS'da nestjs-telegraf.
  • DI/Modul (8.1, 8.2): Update, service, wizard.
  • Guard/Filter 8.6-bob: AdminGuard, exception filter.
  • DB 8.3-bob: foydalanuvchi, buyurtma, savat.
  • Session/Redis 8.15-bob: holat, scene.
  • Navbat 8.22-bob: broadcast, og'ir ish.
  • File/S3 5.11-bob: media.
  • To'lov (Click/Payme): invoice.
  • Web App (11): mini-ilova (React).
  • i18n: ko'p til.

8. Eng yaxshi amaliyotlar (best practices)

  • Token .env (forRootAsync — 14).
  • Update yupqa service (controller kabi — 8.1: 2.7).
  • Session Redis (production — 8.15).
  • Wizard (murakkab dialog — 2.12); answerCbQuery majburiy 2.9-bob.
  • file_id qayta ishlatish (tez — 2.15).
  • Broadcast navbat + rate limit (8.22, 2.22).
  • Guard (admin) + exception filter (8.6, 2.23, 2.24).
  • Webhook production 2.24-bob; polling dev.
  • DB'da foydalanuvchi (start'da ro'yxat — 8.3); i18n (ko'p til).
  • pre_checkout tez (10 soniya — 2.20).

9. Amaliy loyiha: "To'liq Do'kon Telegram Boti"

NestJS bot va barcha imkoniyatlarni mustahkamlash (sizning eng katta loyihangiz).

Maqsad

NestJS'da to'liq do'kon boti: katalog, savat, buyurtma (wizard), to'lov, admin, broadcast — barcha imkoniyatlar bilan.

Talablar (requirements)

  1. Bot setup: TelegrafModule, BotUpdate, /start (DB ro'yxat), menyu (Misol 1, 2.2, 2.4).
  2. Katalog: inline + sahifalash + mahsulot tafsiloti (rasm — Misol 2, 2.8).
  3. Savat: qo'shish, jonli miqdor (editMessageReplyMarkup — Misol 12, 2.16).
  4. Buyurtma wizard: ism telefon (contact) manzil (location) tasdiq (Misol 3, 2.12).
  5. Media: rasm qabul + S3, file_id keshlash (Misol 5, 2.15).
  6. To'lov: invoice pre_checkout successful (Misol 6, 2.20).
  7. So'rovnoma + Web App: baholash, mini-ilova (Misol 7, 2.18, 2.19).
  8. Admin: guard, statistika, panel (Misol 8, 2.23).
  9. Broadcast: navbat + rate limit (Misol 9, 2.22).
  10. Xato + i18n + Redis session: exception filter, ko'p til, holat (Misol 10, 11, 2.24).

Maslahatlar (hint)

  • Session: middlewares [session()] (2.10, 2-xato).
  • answerCbQuery har @Action (2.9, 2-holat).
  • Wizard: ctx.wizard.next/state/leave 2.12-bob.
  • Broadcast: navbat (8.22, 3-holat).
  • To'lov: pre_checkout 10 soniya (2.20, 4-xato).
  • Update yupqa service (8.1: 2.7, 5-holat).

"Tayyor" mezonlari (acceptance criteria)

  • Bot setup + /start + menyu.
  • Katalog (inline, sahifalash, rasm).
  • Savat (jonli miqdor).
  • Buyurtma wizard (contact + location).
  • Media (rasm + S3 + file_id).
  • To'lov (invoice successful).
  • So'rovnoma + Web App.
  • Admin (guard, statistika).
  • Broadcast (navbat).
  • Xato filtri + i18n + Redis session.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda NestJS Telegram bot'ning deyarli barcha imkoniyatini o'rgandik:

Keyingi bob — 8.13-bob: NestJS + MongoDB (Mongoose chuqur). Bot'ni bildik; endi NestJS'da MongoDB (Mongoose — 6.2, 6.3) ni chuqur ko'ramiz: @nestjs/mongoose, schema/model dekoratorlar, relations (ref/populate), aggregation, transaction. SQL (8.3, 8.4) bilan birga — NestJS'da ikkala DB turini to'liq bilasiz.


Foydalanilgan rasmiy/ishonchli manbalar

  • github.com/nestjs-telegraf; npmjs.com/package/nestjs-telegraf (dekoratorlar, scene, wizard, guard)
  • telegraf.js.org (Telegraf v4 — Markup, Context, media, payments, Web App)
  • core.telegram.org/bots/api (Bot API — barcha update turlari, inline, payments, Web App)

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
8.12-bob: NestJS Telegram bot (Telegraf) — barcha imkoniyatlar — Wisar