DEV Community

ILshat Khamitov
ILshat Khamitov

Posted on

4 3

Add support use inlineKeyboard in Telegram bot on NestJS

Links

https://github.com/EndyKaufman/kaufman-bot - source code of bot

https://telegram.me/KaufmanBot - current bot in telegram

https://kaufman-bot.site15.ru/graph - project graph

https://github.com/kaufman-bot/schematics-example - project generated with @kaufman-bot/schematics

https://dev.to/endykaufman/series/16805 - kaufman-bot series articles in dev.io

Description of work

In current post I am update exists "FirstMeeting" command to use inlineKeyboard

Update FirstMeetingService

libs/first-meeting/server/src/lib/first-meeting-services/first-meeting.service.ts

import {
  BotCommandsEnum,
  BotCommandsProvider,
  BotCommandsProviderActionMsg,
  BotCommandsProviderActionResultType,
  BotCommandsToolsService,
  OnContextBotCommands,
} from '@kaufman-bot/core-server';
import { Inject, Injectable } from '@nestjs/common';
import { getText } from 'class-validator-multi-lang';
import { CustomInject } from 'nestjs-custom-injector';
import { TranslatesService } from 'nestjs-translates';
import { Markup } from 'telegraf';
import {
  FirstMeetingConfig,
  FIRST_MEETING_CONFIG,
} from '../first-meeting-config/first-meeting.config';
import {
  FirstMeeting,
  FirstMeetingStorage,
  FIRST_MEETING_STORAGE,
} from './first-meeting.storage';

export const DISABLE_FIRST_MEETING_COMMANDS = 'DISABLE_FIRST_MEETING_COMMANDS';

@Injectable()
export class FirstMeetingService
  implements BotCommandsProvider, OnContextBotCommands
{
  @CustomInject(FIRST_MEETING_STORAGE)
  private readonly firstMeetingStorage!: FirstMeetingStorage;

  constructor(
    @Inject(FIRST_MEETING_CONFIG)
    private readonly firstMeetingConfig: FirstMeetingConfig,
    private readonly botCommandsToolsService: BotCommandsToolsService,
    private readonly translatesService: TranslatesService
  ) {}

  async onContextBotCommands<
    TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
  >(msg: TMsg, ctx): Promise<BotCommandsProviderActionResultType<TMsg>> {
    if (msg?.botContext?.[DISABLE_FIRST_MEETING_COMMANDS]) {
      return null;
    }

    const locale = this.botCommandsToolsService.getLocale(msg, 'en');

    const contextFirstMeeting: Partial<FirstMeeting> =
      msg.botCommandHandlerContext;

    if (
      this.botCommandsToolsService.checkCommands(
        msg.text,
        [this.firstMeetingConfig.name],
        locale
      ) ||
      Object.keys(contextFirstMeeting).length > 0
    ) {
      if (
        this.botCommandsToolsService.checkCommands(
          msg.text || msg.data,
          [
            getText('exit'),
            getText('reset'),
            getText('cancel'),
            getText('stop'),
            getText('end'),
          ],
          locale
        )
      ) {
        await this.firstMeetingStorage.pathUserFirstMeeting({
          telegramUserId: this.botCommandsToolsService.getChatId(msg),
          firstMeeting: {
            ...msg.botCommandHandlerContext,
            status: 'EndMeeting',
            messagesMetadata: { EndMeetingRequest: msg },
          },
        });
        return {
          type: 'text',
          text: this.translatesService.translate(
            getText(`{{close}} Meeting canceled`),
            locale,
            { close: '' }
          ),
          message: msg,
          context: { status: 'EndMeeting' },
          callback: async (result) =>
            await this.firstMeetingStorage.pathUserFirstMeeting({
              telegramUserId: this.botCommandsToolsService.getChatId(msg),
              firstMeeting: {
                messagesMetadata: { EndMeetingResponse: result },
              },
            }),
        };
      }

      if (contextFirstMeeting?.status === 'AskFirstname') {
        const text = this.translatesService.translate(
          getText(`What is your last name?`),
          locale
        );
        const currentFirstMeeting =
          await this.firstMeetingStorage.pathUserFirstMeeting({
            telegramUserId: this.botCommandsToolsService.getChatId(msg),
            firstMeeting: {
              messagesMetadata: { AskFirstnameRequest: msg },
            },
          });
        const firstname = this.prepareText(msg.text, locale) || 'Unknown';

        if (currentFirstMeeting?.messagesMetadata?.AskFirstnameResponse) {
          await ctx.telegram.editMessageText(
            currentFirstMeeting.messagesMetadata.AskFirstnameResponse.chat.id,
            currentFirstMeeting.messagesMetadata.AskFirstnameResponse
              .message_id,
            undefined,
            currentFirstMeeting.messagesMetadata.AskFirstnameResponse
              ? `${
                  currentFirstMeeting.messagesMetadata.AskFirstnameResponse.text
                } (${this.translatesService.translate(
                  getText('Your answer'),
                  locale
                )}: ${firstname})`
              : currentFirstMeeting.messagesMetadata.AskFirstnameResponse
          );
        }
        return {
          type: 'text',
          text,
          message: msg,
          context: <Partial<FirstMeeting>>{
            ...msg.botCommandHandlerContext,
            status: 'AskLastname',
            firstname,
          },
          custom: {
            ...Markup.inlineKeyboard([
              Markup.button.callback(
                '➡️' +
                  this.translatesService.translate(getText('Next'), locale),
                'next'
              ),
              Markup.button.callback(
                '' +
                  this.translatesService.translate(getText('Cancel'), locale),
                'exit'
              ),
            ]),
          },
          callback: async (result) =>
            await this.firstMeetingStorage.pathUserFirstMeeting({
              telegramUserId: this.botCommandsToolsService.getChatId(msg),
              firstMeeting: {
                messagesMetadata: { AskLastnameResponse: result },
              },
            }),
        };
      }

      if (contextFirstMeeting?.status === 'AskLastname') {
        const text = this.translatesService.translate(
          getText(`What is your gender?`),
          locale
        );
        const currentFirstMeeting =
          await this.firstMeetingStorage.pathUserFirstMeeting({
            telegramUserId: this.botCommandsToolsService.getChatId(msg),
            firstMeeting: {
              messagesMetadata: { AskLastnameRequest: msg },
            },
          });
        const lastname = this.prepareText(msg.text, locale);
        if (currentFirstMeeting?.messagesMetadata?.AskLastnameResponse) {
          await ctx.telegram.editMessageText(
            currentFirstMeeting.messagesMetadata.AskLastnameResponse.chat.id,
            currentFirstMeeting.messagesMetadata.AskLastnameResponse.message_id,
            undefined,
            currentFirstMeeting.messagesMetadata.AskLastnameResponse
              ? `${
                  currentFirstMeeting.messagesMetadata.AskLastnameResponse.text
                } (${this.translatesService.translate(
                  getText('Your answer'),
                  locale
                )}: ${lastname})`
              : currentFirstMeeting.messagesMetadata.AskLastnameResponse
          );
        }
        return {
          type: 'text',
          text,
          message: msg,
          context: <Partial<FirstMeeting>>{
            ...msg.botCommandHandlerContext,
            status: 'AskGender',
            lastname,
          },
          custom: {
            ...Markup.inlineKeyboard([
              Markup.button.callback(
                '🚹' +
                  this.translatesService.translate(getText('Male'), locale),
                'male'
              ),
              Markup.button.callback(
                '🚺' +
                  this.translatesService.translate(getText('Female'), locale),
                'female'
              ),
              Markup.button.callback(
                '' +
                  this.translatesService.translate(getText('Cancel'), locale),
                'exit'
              ),
            ]),
          },
          callback: async (result) =>
            await this.firstMeetingStorage.pathUserFirstMeeting({
              telegramUserId: this.botCommandsToolsService.getChatId(msg),
              firstMeeting: {
                messagesMetadata: { AskGenderResponse: result },
              },
            }),
        };
      }

      if (contextFirstMeeting?.status === 'AskGender') {
        const firstMeeting: Partial<FirstMeeting> = {
          ...contextFirstMeeting,
          status: 'EndMeeting',
          gender: this.botCommandsToolsService.checkCommands(
            this.prepareText(msg.data || msg.text, locale),
            [getText('female'), getText('fm'), getText('f')],
            locale
          )
            ? 'Female'
            : 'Male',
          messagesMetadata: { AskGenderRequest: msg },
        };
        const currentFirstMeeting =
          await this.firstMeetingStorage.pathUserFirstMeeting({
            telegramUserId: this.botCommandsToolsService.getChatId(msg),
            firstMeeting,
          });
        if (currentFirstMeeting?.messagesMetadata?.AskGenderResponse) {
          await ctx.telegram.editMessageText(
            currentFirstMeeting.messagesMetadata.AskGenderResponse.chat.id,
            currentFirstMeeting.messagesMetadata.AskGenderResponse.message_id,
            undefined,
            currentFirstMeeting.messagesMetadata.AskGenderResponse
              ? `${
                  currentFirstMeeting.messagesMetadata.AskGenderResponse.text
                } (${this.translatesService.translate(
                  getText('Your answer'),
                  locale
                )}: ${this.translatesService.translate(
                  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                  firstMeeting.gender!,
                  locale
                )})`
              : currentFirstMeeting.messagesMetadata.AskGenderResponse
          );
        }
        return {
          type: 'text',
          text: this.translatesService.translate(
            this.botCommandsToolsService.getRandomItem([
              getText(
                `Nice to meet you, {{meetGender}} {{firstname}} {{lastname}} {{vulcan}}`
              ),
              getText(`Nice to meet you, {{firstname}} {{vulcan}}`),
            ]),
            locale,
            {
              vulcan: '🖖',
              ...firstMeeting,
              meetGender: this.mapGenderToMeetGender(firstMeeting, locale),
              firstname: this.botCommandsToolsService.capitalizeFirstLetter(
                firstMeeting.firstname,
                locale
              ),
              lastname: this.botCommandsToolsService.capitalizeFirstLetter(
                firstMeeting.lastname,
                locale
              ),
            }
          ),
          message: msg,
          context: <Partial<FirstMeeting>>{ status: 'EndMeeting' },
          callback: async (result) =>
            await this.firstMeetingStorage.pathUserFirstMeeting({
              telegramUserId: this.botCommandsToolsService.getChatId(msg),
              firstMeeting: {
                messagesMetadata: { EndMeetingResponse: result },
              },
            }),
        };
      }
    }

    return null;
  }

  async onHelp<
    TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
  >(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
    return await this.onMessage({
      ...msg,
      text: `${this.firstMeetingConfig.name} ${BotCommandsEnum.help}`,
    });
  }

  async onMessage<
    TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
  >(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
    if (msg?.botContext?.[DISABLE_FIRST_MEETING_COMMANDS]) {
      return null;
    }

    const locale = this.botCommandsToolsService.getLocale(msg, 'en');

    const firstMeeting = await this.firstMeetingStorage.getUserFirstMeeting({
      telegramUserId: this.botCommandsToolsService.getChatId(msg),
    });
    const spyWord = this.firstMeetingConfig.spyWords.find((spyWord) =>
      this.botCommandsToolsService.checkCommands(msg.text, [spyWord], locale)
    );
    if (spyWord) {
      if (
        this.botCommandsToolsService.checkCommands(
          msg.text,
          [BotCommandsEnum.help],
          locale
        )
      ) {
        return {
          type: 'markdown',
          message: msg,
          markdown: this.botCommandsToolsService.generateHelpMessage(msg, {
            locale,
            name: this.firstMeetingConfig.title,
            descriptions: this.firstMeetingConfig.descriptions,
            usage: this.firstMeetingConfig.usage,
            category: this.firstMeetingConfig.category,
          }),
        };
      }

      if (
        this.botCommandsToolsService.checkCommands(
          msg.text,
          [BotCommandsEnum.reset],
          locale
        )
      ) {
        await this.firstMeetingStorage.removeUserFirstMeeting({
          telegramUserId: this.botCommandsToolsService.getChatId(msg),
        });

        return {
          type: 'text',
          text: this.translatesService.translate(
            this.botCommandsToolsService.getRandomItem([
              getText('Your meeting information has been deleted {{unamused}}'),
              getText('I forgot about your existence {{worried}}'),
            ]),
            locale,
            {
              unamused: '😒',
              worried: '😟',
            }
          ),
          message: msg,
        };
      }

      if (
        !firstMeeting &&
        this.botCommandsToolsService.checkCommands(
          msg.text,
          [getText('start')],
          locale
        )
      ) {
        const text = this.translatesService.translate(
          this.botCommandsToolsService.getRandomItem([
            getText(`Hey! I'm {{botName}} {{smile}}, what's your name?`),
            getText(`Hey! what's your name?`),
          ]),
          locale,
          {
            botName: this.firstMeetingConfig.botName[locale],
            smile: '🙂',
          }
        );
        await this.firstMeetingStorage.pathUserFirstMeeting({
          telegramUserId: this.botCommandsToolsService.getChatId(msg),
          firstMeeting: {
            status: 'AskFirstname',
            firstname: '',
            lastname: '',
            gender: 'Male',
          },
        });
        return {
          type: 'text',
          text,
          message: msg,
          context: <Partial<FirstMeeting>>{ status: 'AskFirstname' },
          custom: {
            ...Markup.inlineKeyboard([
              Markup.button.callback(
                '➡️' +
                  this.translatesService.translate(getText('Next'), locale),
                'next'
              ),
              Markup.button.callback(
                '' +
                  this.translatesService.translate(getText('Cancel'), locale),
                'exit'
              ),
            ]),
          },
          callback: async (result) =>
            await this.firstMeetingStorage.pathUserFirstMeeting({
              telegramUserId: this.botCommandsToolsService.getChatId(msg),
              firstMeeting: {
                messagesMetadata: { AskFirstnameResponse: result },
              },
            }),
        };
      }
    }

    if (
      firstMeeting?.status === 'EndMeeting' &&
      this.botCommandsToolsService.checkCommands(
        msg.text,
        [getText('hi'), getText('hello'), getText('hey')],
        locale
      )
    ) {
      return {
        type: 'markdown',
        markdown: this.translatesService
          .translate(
            this.botCommandsToolsService.getRandomItem([
              getText(
                `Hello {{meetGender}} {{firstname}} {{lastname}} {{vulcan}}`
              ),
              getText(`Hello {{firstname}} {{lastname}} {{handsplayed}}`),
              getText(`I'm glad to see you {{firstname}} {{wink}}`),
              getText(`Hi {{firstname}} {{vulcan}}`),
            ]),
            locale,
            {
              vulcan: '🖖',
              handsplayed: '🖐',
              wink: '😉',
              ...firstMeeting,
              meetGender: this.mapGenderToMeetGender(firstMeeting, locale),
              firstname: this.botCommandsToolsService.capitalizeFirstLetter(
                firstMeeting.firstname,
                locale
              ),
              lastname: this.botCommandsToolsService.capitalizeFirstLetter(
                firstMeeting.lastname,
                locale
              ),
            }
          )
          .split('  ')
          .join(' ')
          .split('  ')
          .join(' '),
        message: msg,
        context: {},
      };
    }

    return null;
  }

  private mapGenderToMeetGender(
    firstMeeting: Partial<FirstMeeting>,
    locale: string
  ) {
    return this.translatesService.translate(
      firstMeeting.gender === 'Female' ? getText('Madam') : getText('Sir'),
      locale
    );
  }

  private prepareText(text: string, locale: string) {
    if (
      this.botCommandsToolsService.checkCommands(
        text,
        [getText('skip'), getText('next')],
        locale
      )
    ) {
      return '';
    }
    return this.botCommandsToolsService
      .clearCommands(
        text,
        [
          getText('I'),
          getText('hi'),
          getText('hello'),
          getText('hey'),
          getText('am'),
          getText('my'),
          getText('is'),
          getText('name'),
          getText('lastname'),
          getText('firstname'),
          getText('last'),
          getText('first'),
        ],
        locale
      )
      .trim();
  }
}

Enter fullscreen mode Exit fullscreen mode

Work before update

Work after update

I'm switching to developing a monetization bot, I will publish general application commands that will be developed in the process of working on a monetization bot, business commands will be in a private repository, the next post will not be soon, thanks for reading...

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay