robot-mafia / nestjs-telegraf

🤖 Powerful Nest module for easy and fast creation Telegram bots
https://nestjs-telegraf.0x467.com
MIT License
520 stars 89 forks source link

How to use wizard? #350

Closed meowlanguages closed 3 years ago

meowlanguages commented 3 years ago

I have no idea how to use wizard on my scene. Please add more examples.

evilsprut commented 3 years ago

Hey @meowlanguages! I've never used the Wizard and probably can't help it, but you can do whatever you want with Telegraf by injecting Telegraf instance directly with @InjectBot() decorator.

Also, @Morb0 may be you can help?

Morb0 commented 3 years ago

You can inject TELEGRAF_STAGE provider, create a wizard scene as usual, and register it in Stage.

Example:

constructor(
    @Inject(TELEGRAF_STAGE) stage: Scenes.Stage<Context>,
  ) {
    const scene = new Scenes.WizardScene<Context>(
      'youSceneIdentifier',
      this.createStepHandler1(),
      this.createStepHandler2(),
      // other steps...
    );

    stage.register(scene);
  }
evilsprut commented 3 years ago

@meowlanguages problem solved?

felinto-dev commented 3 years ago

Hey @bukhalo 👋

Thanks for helping the community!

I'm trying to use wizard scenes too and I'm facing a lot of difficulty due to the lack of documentation and practical examples.

It's not your fault of course, but to the extent of your time and availability, could you consider adding examples of this? Much appreciated.

You can inject TELEGRAF_STAGE provider, create a wizard scene as usual, and register it in Stage.

Example:

constructor(
    @Inject(TELEGRAF_STAGE) stage: Scenes.Stage<Context>,
  ) {
    const scene = new Scenes.WizardScene<Context>(
      'youSceneIdentifier',
      this.createStepHandler1(),
      this.createStepHandler2(),
      // other steps...
    );

    stage.register(scene);
  }

My big question is how to create a "Wizard Scene" in "nest way" in a way that is scalable and using the decorators that this module offers. I'm sorry but I couldn't understand how it's possible to do this at first.

https://github.com/telegraf/telegraf/issues/705#issuecomment-549056045

I was checking some links about this and I'm facing the same difficulty posed in this example.

I need to request user data like Phone Number, Email, and Home address using Wizard Scene. I imagine this is a common use case so I think it would be very interesting for the community to have an example of this using Telegraf + Wizard Scenes.

filipmacek commented 3 years ago

Yes, agree. Missing wizard support and docs in nest way.

felinto-dev commented 3 years ago

@Morb0 use wizard scene does not work because "TypeScript types". Could you help me?

import { Inject } from '@nestjs/common';
import { Scene, TELEGRAF_STAGE } from 'nestjs-telegraf';
import { Scenes } from 'telegraf';

import { Context } from '@/tg/common/interfaces';

@Scene('userPersonalDataWizard')
export class UserPersonalDataWizard {
  constructor(@Inject(TELEGRAF_STAGE) stage: Scenes.Stage<Context>) {
    const scene = new Scenes.WizardScene<Scenes.WizardContext>(
      'userPersonalDataWizard',
      this.hello(),
      // this.createStepHandler2(),
      // other steps...
    );

    stage.register(scene);
  }

  hello() {
    return 'heyyyyyyyyy!';
  }
}

Screenshot_917

Screenshot_918

No overload matches this call.
  Overload 1 of 2, '(id: string, ...steps: Middleware<WizardContext<WizardSessionData>>[]): WizardScene<WizardContext<WizardSessionData>>', gave the following error.
    Argument of type 'string' is not assignable to parameter of type 'Middleware<WizardContext<WizardSessionData>>'.
  Overload 2 of 2, '(id: string, options: SceneOptions<WizardContext<WizardSessionData>>, ...steps: Middleware<WizardContext<WizardSessionData>>[]): WizardScene<...>', gave the following error.
    Argument of type 'string' is not assignable to parameter of type 'SceneOptions<WizardContext<WizardSessionData>>'.ts(2769)

What TypeScript types should I use? Could you send a full example and not only a code snippet, please?

Morb0 commented 3 years ago

@felinto-dev As types says, you need return middleware function from hello method. Somthing like this:

hello() {
  return (ctx) => {
    ctx.reply('Hello World');
    ctx.wizard.next();
  }
}
felinto-dev commented 3 years ago

I would like to say thank you so much for your help!

Once this example is working I will post the full code here to help the community. Unfortunately, I'm not getting into the scene.

I get the following error: "Cannot read property 'enter' of undefined"

Screenshot_919

Another question is that you notice that I commented "@Scene('userPersonalDataWizard')". Should I repeat the name of the scene twice?

felinto-dev commented 3 years ago

I guess I need to use "stage middleware", however, this example does not works

https://github.com/bukhalo/nestjs-telegraf/issues/72#issuecomment-623085469

    import { Stage } from 'nestjs-telegraf';
    const stage = new Stage([superWizard], { default: 'super-wizard' });
    this.bot.use(stage.middleware());

Screenshot_920

felinto-dev commented 3 years ago

After hours of trying to find a solution, I saw what the error was, which is not obvious at first glance. Also, why session middleware is deprecated. https://github.com/telegraf/telegraf/issues/1372#issuecomment-782668499

It is necessary to activate the session middleware for the scene wizard to work.

https://github.com/telegraf/telegraf/issues/309#issuecomment-362870720

https://github.com/RealSpeaker/telegraf-session-local/issues/153

viacheslavsaloid commented 3 years ago

Hello

Thank you very much for integrating Scenes in Decorators. It works really awesome.

It will be also helpful to add Wizard functionality

Maybe in this way ?

@WizardScene()
class ProfileWizardScene {

    Step('1') 
    ageStep() {

        @Enter
        ageStepEnter(ctx) {
            ctx.reply('Age Step Enter')
        }

        @On('message')
        ageMessageHandler(ctx) {
            ctx.reply('Age Saved')
            ctx.wizard.next();
        }

    }

    Step('2') 
    nameStep() {

        @Enter
        nameStepEnter(ctx) {
            ctx.reply('Name Step Enter')
        }

        @On('message')
        nameMessageHandler(ctx) {
            ctx.reply('Name Saved')
            ctx.wizard.next();
        }
    }
}
evilsprut commented 3 years ago

Wizard support added in v2.4.0, see on @xTCry code for example.

felinto-dev commented 3 years ago

Wizard support added in v2.4.0, see on @xTCry code for example.

Could you send the direct link for example please?

felinto-dev commented 3 years ago

Maybe I found it?

Look the README.MD

https://github.com/xTCry/ts-telegraf-decorators

ahnennyi commented 3 years ago

Wizard support added in v2.4.0, see on @xTCry code for example.

Could you send the direct link for example please?

Working example: https://github.com/bukhalo/nestjs-telegraf/pull/469#issuecomment-902583496

Simplified example:

  1. Create a wizard using Wizard and WizardStep decorators.
    
    import {  Context, Wizard,  WizardStep } from 'nestjs-telegraf'
    import { Scenes } from 'telegraf'

@Wizard('test') export class TestWizard { @WizardStep(1) step1(@Context() ctx: Scenes.WizardContext) { ctx.reply('first scene') ctx.wizard.next() }

@WizardStep(2) async step2(@Context() ctx: Scenes.WizardContext) { ctx.reply('last scene') ctx.scene.leave() } }


2. Configure TelegrafModule and add TestWizard to your module providers.

import { session } from 'telegraf' @Module({ imports: [ TelegrafModule.forRoot({ token: 'YOUR_TELEGRAM_TOKEN', middlewares: [session()], }), ], providers: [TestWizard], }) export class MyTelegramModule {}


3. Enter wizard by calling ctx.scene.enter, e.g.:

@Start() start(ctx: Scenes.SceneContext) { ctx.scene.enter('test') }

StackTraceYo commented 2 years ago

is there an example with callback_data in a button. im not able to get any data from the callback buttons when i move between steps

ahnennyi commented 2 years ago

is there an example with callback_data in a button. im not able to get any data from the callback buttons when i move between steps

If I understood you correctly, here is an example:

import { Context, Wizard,  WizardStep } from 'nestjs-telegraf'
import { Scenes } from 'telegraf'

@Wizard('test')
export class TestWizard {
  @WizardStep(1)
  step1(@Context() ctx: Scenes.WizardContext) {
    const randomData = Math.random()
    ctx.reply('Press the button', {
        reply_markup: {
          inline_keyboard: [
            [{ text: 'Press me', callback_data: `action:${randomData}` }],
          ],
        },
        reply_to_message_id: ctx.message.message_id,
      })
  }

  @Action(/action:.+/)
  button(@Context() ctx: any) {
    const [, data] = ctx.callbackQuery.data.split(':')
    ctx.answerCbQuery()
    // save data for next step in ctx.wizard.state
    ctx.wizard.state.data = data
    ctx.wizard.next()
    ctx.wizard.steps[ctx.wizard.cursor](ctx)
  }

  @WizardStep(2)
  async step2(@Context() ctx: Scenes.WizardContext) {
    // ctx.wizard.state contains stored data
    console.log((ctx.wizard.state as any).data)
    ctx.scene.leave()
  }
}

Note that using ctx.wizard.steps[ctx.wizard.cursor](ctx) is a little hack to call step 2 immediately after pressing the button. Without this user would need to send another message to call step 2.