Telegram Bot — davomi

~25 daq · NestJS · Qo'shimcha xizmatlar

Muammodan boshlaylik

Oldingi darsda oddiy botni yozdik. Lekin real bot murakkabroq:

  • Foydalanuvchi ro'yxatdan o'tishi kerak
  • Bot holat (state) ni eslab qolishi kerak: "siz hozir mahsulot qo'shish jarayonidasiz"
  • Webhook orqali ishlash kerak (polling production da emas)

Bu darsda shu narsalarni ko'ramiz.


Wizard Scene — ko'p bosqichli suhbat

Foydalanuvchi mahsulot qo'shmoqda. Bot unga ketma-ket savollar beradi:

Bot: Mahsulot nomini yozing
Foydalanuvchi: iPhone 15
Bot: Narxini yozing
Foydalanuvchi: 12000000
Bot: Mahsulot qo'shildi! ✅

Bu Wizard Scene — ko'p bosqichli so'roq.


O'rnatish

npm install nestjs-telegraf telegraf

Wizard Scene yaratish

// bot/scenes/add-product.scene.ts
import { Wizard, WizardStep, Ctx, Message } from 'nestjs-telegraf';
import { WizardContext } from 'telegraf/typings/scenes';

@Wizard('add-product') // ← scene nomi
export class AddProductScene {

  // 1-qadam: nom so'rash
  @WizardStep(1)
  async step1(@Ctx() ctx: WizardContext) {
    await ctx.reply('Mahsulot nomini yozing:');
    ctx.wizard.next(); // ← keyingi qadamga o'tish
  }

  // 2-qadam: nom saqlash, narx so'rash
  @WizardStep(2)
  async step2(
    @Ctx() ctx: WizardContext,
    @Message('text') name: string,
  ) {
    ctx.wizard.state['name'] = name; // ← nomni eslab qolish
    await ctx.reply('Narxini yozing (so\'mda):');
    ctx.wizard.next();
  }

  // 3-qadam: narx saqlash, tugatish
  @WizardStep(3)
  async step3(
    @Ctx() ctx: WizardContext,
    @Message('text') price: string,
  ) {
    const name = ctx.wizard.state['name'];
    const priceNum = Number(price);

    if (isNaN(priceNum)) {
      await ctx.reply('Narx raqam bo\'lishi kerak. Qayta yozing:');
      return; // ← qadamni o'zgartirmaymiz
    }

    // Mahsulot saqlash
    await this.productsService.create({ name, price: priceNum });

    await ctx.reply(`✅ "${name}" — ${priceNum.toLocaleString()} so'm qo'shildi!`);
    await ctx.scene.leave(); // ← sceneni tugatish
  }
}

Scene ni modulga qo'shish

// bot/bot.module.ts
import { TelegrafModule } from 'nestjs-telegraf';

@Module({
  imports: [
    TelegrafModule.forFeature([AddProductScene]),
  ],
  providers: [BotUpdate, AddProductScene],
})
export class BotModule {}

Scene ni ishga tushirish

// bot/bot.update.ts
@Update()
export class BotUpdate {

  @Hears('➕ Mahsulot qo\'shish')
  async addProduct(@Ctx() ctx: Context) {
    await (ctx as any).scene.enter('add-product'); // ← scenega kirish
  }
}

Foydalanuvchini Telegram ID bilan bog'lash

Foydalanuvchi bot ga yozganida uning Telegram ID si bo'ladi. Buni database dagi foydalanuvchi bilan bog'lash kerak.

// users/user.entity.ts
@Entity()
export class User {
  @Column({ nullable: true, unique: true })
  telegramId: number;
}
// bot/bot.update.ts
@Start()
async start(@Ctx() ctx: Context) {
  const telegramId = ctx.from.id;
  const name = ctx.from.first_name;

  // Foydalanuvchi bormi?
  let user = await this.usersService.findByTelegramId(telegramId);

  if (!user) {
    // Yangi foydalanuvchi — ro'yxatdan o'tkazish
    user = await this.usersService.createFromTelegram({
      telegramId,
      name,
    });
    await ctx.reply(`Xush kelibsiz, ${name}! Siz ro'yxatdan o'tdingiz.`);
  } else {
    await ctx.reply(`Xush kelibsiz qaytib, ${name}!`);
  }
}

Webhook sozlash (Production)

Development da polling yaxshi. Production da — webhook.

// app.module.ts
TelegrafModule.forRoot({
  token: process.env.TELEGRAM_BOT_TOKEN,
  launchOptions: {
    webhook: {
      domain: 'https://yourdomain.com',  // ← HTTPS kerak!
      path: '/telegram/webhook',
    },
  },
})

Webhook faqat HTTPS bilan ishlaydi. Lokalni test qilish uchun ngrok ishlating:

ngrok http 3000
# → https://abc123.ngrok.io

Xabar formatlash

// HTML formatlash
await ctx.replyWithHTML(
  '<b>Buyurtma:</b> #123\n' +
  '<i>Holat:</i> Yetkazilmoqda\n' +
  '<code>Track ID: UZ123456</code>'
);

// Markdown
await ctx.replyWithMarkdown(
  '*Buyurtma:* #123\n' +
  '_Holat:_ Yetkazilmoqda'
);

// Rasm bilan
await ctx.replyWithPhoto(
  { url: 'https://example.com/product.jpg' },
  { caption: 'iPhone 15 Pro' }
);

Bot Middleware — Guard

Faqat ro'yxatdan o'tgan foydalanuvchilarga ruxsat:

// bot/guards/auth.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { TelegrafExecutionContext } from 'nestjs-telegraf';

@Injectable()
export class TelegramAuthGuard implements CanActivate {
  constructor(private usersService: UsersService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const ctx = TelegrafExecutionContext.create(context).getContext();
    const telegramId = ctx.from?.id;

    if (!telegramId) return false;

    const user = await this.usersService.findByTelegramId(telegramId);
    return !!user;
  }
}
@Hears('/profile')
@UseGuards(TelegramAuthGuard)
async profile(@Ctx() ctx: Context) {
  // Faqat ro'yxatdan o'tganlar ko'radi
}

Tez-tez uchraydigan xatolar

1. Scene ishlamayapti Sababi: Scene TelegrafModule.forFeature([Scene]) ga qo'shilmagan. Yechim: Module da import va provider ga qo'shing.


2. Webhook SSL xatosi

Error: HTTPS required for webhook

Sababi: HTTP ishlatyapsiz. Yechim: Nginx + SSL, yoki ngrok bilan test qiling.


3. Bir xabar ikki marta qayta ishlanmoqda Sababi: Polling va webhook bir vaqtda ishlamoqda. Yechim: Faqat bittasini ishlating.


Xulosa

Eslab qoling:

  • @Wizard() — ko'p bosqichli suhbat
  • ctx.wizard.state — bosqichlar orasida ma'lumot saqlash
  • ctx.scene.leave() — sceneni tugatish
  • Telegram ID orqali foydalanuvchini database bilan bog'lang
  • Production da webhook, development da polling

Keyingi dars: 51-dars: To'lov tizimlari — Click / Payme / Stripe