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)
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
// 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 {}TelegrafModule —
forRootAsync(token env'dan — 14),middlewares(session — scene uchun — 2.10),include(bot modullari). NestJS DI bilan integratsiya. Bir nechta bot ham mumkin (botNamebilan har biriga alohida token — multi-bot). Bu — botning markaziy sozlamasi.
2.3. Update turlari (barcha) — eng muhim jadval
┌─────────────────────────────────────────────────────────────┐
│ 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
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)
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)
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)
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 moslashReply keyboard (5.14: 2.8) — klaviatura ustidagi tugmalar (matn yuboradi —
@Hearsbilan 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)
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).callbackdata ("buy_1") —@Actionbilan ushlanadi. Interaktiv UI uchun (savat, sahifalash).
2.9. Callback query (@Action — inline tugma ishlovi)
@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)
// 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
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 leaveScene — 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)
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)
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/@Actionhandler'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 sifatidaBotModule'ga qo'shiladi (Misol 4).
2.13. Kontakt qabul qilish (telefon)
@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").contactRequesttugmasi 2.7-bob bilan so'raladi foydalanuvchi telefonini yuboradictx.message.contact.phone_number. Ro'yxatdan o'tish, OTP'siz tasdiqlash 5.18-bob uchun.removeKeyboard(klaviaturani yashirish).
2.14. Joylashuv (location)
@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").locationRequesttugmasi 2.7-bob bilanctx.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)
// 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_idgetFileLink(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
// 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)
@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")qidiruvanswerInlineQuery(natijalar). Foydalanuvchi natijani tanlab, istalgan chatga yuboradi. Qidiruv botlari (GIF, musiqa, mahsulot ulashish) uchun. BotFather'da yoqiladi.
2.18. So'rovnoma va quiz (poll)
// 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)
// 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)
// 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'lov —
replyWithInvoice(hisob — provayder token BotFather'dan: Click/Payme/Stripe). Oqim: invoyspre_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
@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)
// 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)
// 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— botctx'ini olish (HTTPRequesto'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):
// 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)
},
],
}),
}),Middleware —
next()majburiy chaqiriladi (aks holda handler ishlamaydi). Tartib muhim:session()custom middleware'dan oldin turishi kerak (ctx.sessiontayyor bo'lishi uchun). Foydalanuvchini bir marta DB'dan yuklabctx.state'ga qo'yish (har handler'da qayta so'ramaslik) uchun ideal.
2.24. Xatolarni boshqarish va Webhook (prod)
// 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 (@InjectBotorqali sozlanadi): NestJS filtri ushlamay qolgan har qanday xatoning oxirgi "himoya to'ri". Odatda@Catchfiltri 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)
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
// 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)
// 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)
@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)
@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)
// 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 oladiMisol 5 — Media: rasm qabul + S3 (2.15, 5.11)
@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)
@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)
// 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)
// 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)
// 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)
// 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)
@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)
// 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
kodda token (14)
.env (forRootAsync)2) answerCbQuery'siz inline tugma
tugma "yuklanmoqda" qotib qoladi (2.9)
ctx.answerCbQuery() har @Action'da3) Broadcast to'g'ridan-to'g'ri siklda
for sendMessage (rate limit — ban — 2.22)
navbat (BullMQ) + concurrency limit4) Session xotirada (production)
default session (restart yo'qoladi — 2.10)
Redis session (8.15)5) Update'da og'ir mantiq
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)
- Bot setup: TelegrafModule, BotUpdate, /start (DB ro'yxat), menyu (Misol 1, 2.2, 2.4).
- Katalog: inline + sahifalash + mahsulot tafsiloti (rasm — Misol 2, 2.8).
- Savat: qo'shish, jonli miqdor (editMessageReplyMarkup — Misol 12, 2.16).
- Buyurtma wizard: ism telefon (contact) manzil (location) tasdiq (Misol 3, 2.12).
- Media: rasm qabul + S3, file_id keshlash (Misol 5, 2.15).
- To'lov: invoice pre_checkout successful (Misol 6, 2.20).
- So'rovnoma + Web App: baholash, mini-ilova (Misol 7, 2.18, 2.19).
- Admin: guard, statistika, panel (Misol 8, 2.23).
- Broadcast: navbat + rate limit (Misol 9, 2.22).
- 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:
- Asos: TelegrafModule 2.2-bob, @Update/Ctx 2.4-bob, barcha update turlari 2.3-bob, javob metodlari 2.5-bob, formatlash 2.6-bob.
- Tugmalar: reply keyboard 2.7-bob, inline keyboard 2.8-bob, @Action callback 2.9-bob.
- Holat/dialog: session 2.10-bob, Scene/Wizard — Wizard (bosqichma-bosqich — 2.12) va Base Scene (erkin — 2.12b).
- Kirish turlari: kontakt 2.13-bob, joylashuv 2.14-bob, media (rasm/video/ovoz/hujjat — 2.15), so'rovnoma 2.18-bob, inline rejim 2.17-bob.
- Ilg'or: xabar tahrir/o'chir/pin 2.16-bob, Web App 2.19-bob, to'lov 2.20-bob, guruh/admin 2.21-bob, broadcast 2.22-bob.
- NestJS integratsiya: guard/middleware 2.23-bob, exception filter va
bot.catch2.24-bob, webhook/polling 2.24-bob, best practices 2.25-bob.
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!