From 5671ad5b02cd03ac56e611b99031298d36476b2f Mon Sep 17 00:00:00 2001 From: vchikalkin Date: Sun, 7 Sep 2025 15:50:34 +0300 Subject: [PATCH] refactor(bot): update conversation handling and middleware integration - Replaced the previous conversations middleware with the Grammy framework's implementation. - Introduced a loop to register custom conversations, enhancing the bot's conversation management capabilities. - Removed the addContact feature implementation from the add-contact file, streamlining the codebase. --- apps/bot/src/bot/conversations/add-contact.ts | 87 ++++++++++++++++++ apps/bot/src/bot/conversations/index.ts | 1 + apps/bot/src/bot/features/add-contact.ts | 88 ------------------- apps/bot/src/bot/index.ts | 9 +- 4 files changed, 95 insertions(+), 90 deletions(-) create mode 100644 apps/bot/src/bot/conversations/add-contact.ts create mode 100644 apps/bot/src/bot/conversations/index.ts diff --git a/apps/bot/src/bot/conversations/add-contact.ts b/apps/bot/src/bot/conversations/add-contact.ts new file mode 100644 index 0000000..479f388 --- /dev/null +++ b/apps/bot/src/bot/conversations/add-contact.ts @@ -0,0 +1,87 @@ +/* eslint-disable id-length */ +import { type Context } from '@/bot/context'; +import { KEYBOARD_REMOVE, KEYBOARD_SHARE_BOT, KEYBOARD_SHARE_PHONE } from '@/config/keyboards'; +import { isCustomerMaster } from '@/utils/customer'; +import { isValidPhoneNumber, normalizePhoneNumber } from '@/utils/phone'; +import { type Conversation } from '@grammyjs/conversations'; +import { CustomersService } from '@repo/graphql/api/customers'; +import { RegistrationService } from '@repo/graphql/api/registration'; + +export async function addContact(conversation: Conversation, ctx: Context) { + // Проверяем, что пользователь является мастером + const telegramId = ctx.from?.id; + if (!telegramId) { + return ctx.reply(await conversation.external(({ t }) => t('err-generic'))); + } + + const customerService = new CustomersService({ telegramId }); + const { customer } = await customerService.getCustomer({ telegramId }); + + if (!customer) { + return ctx.reply( + await conversation.external(({ t }) => t('msg-need-phone')), + KEYBOARD_SHARE_PHONE, + ); + } + + if (!isCustomerMaster(customer)) { + return ctx.reply(await conversation.external(({ t }) => t('msg-not-master'))); + } + + // Просим отправить контакт клиента + await ctx.reply(await conversation.external(({ t }) => t('msg-send-client-contact'))); + + // Ждем любое сообщение от пользователя + const waitCtx = await conversation.wait(); + + // Проверяем команду отмены + if (waitCtx.message?.text === '/cancel') { + return ctx.reply(await conversation.external(({ t }) => t('msg-cancel'))); + } + + // Проверяем, что отправлен контакт + if (!waitCtx.message?.contact) { + return ctx.reply(await conversation.external(({ t }) => t('msg-send-contact'))); + } + + const { contact } = waitCtx.message; + const name = `${contact.first_name || ''} ${contact.last_name || ''}`.trim(); + const phone = normalizePhoneNumber(contact.phone_number); + + // Проверяем валидность номера телефона + if (!isValidPhoneNumber(phone)) { + return ctx.reply(await conversation.external(({ t }) => t('msg-invalid-phone'))); + } + + try { + // Проверяем, есть ли клиент с таким номером + const { customer: existingCustomer } = await customerService.getCustomer({ phone }); + let documentId = existingCustomer?.documentId; + + // Если клиента нет, создаём нового + if (!documentId) { + const registrationService = new RegistrationService(); + const createCustomerResult = await registrationService.createCustomer({ name, phone }); + + documentId = createCustomerResult?.createCustomer?.documentId; + if (!documentId) throw new Error('Клиент не создан'); + } + + // Добавляем текущего мастера к клиенту + const masters = [customer.documentId]; + await customerService.addMasters({ data: { masters }, documentId }); + + // Отправляем подтверждения и инструкции + await ctx.reply(await conversation.external(({ t }) => t('msg-contact-added', { name }))); + await ctx.reply(await conversation.external(({ t }) => t('msg-contact-forward'))); + await ctx.reply(await conversation.external(({ t }) => t('msg-share-bot')), KEYBOARD_SHARE_BOT); + } catch (error) { + await ctx.reply( + await conversation.external(({ t }) => t('err-with-details', { error: String(error) })), + ); + } finally { + await ctx.reply(await conversation.external(({ t }) => t('commands-list')), KEYBOARD_REMOVE); + } + + return ctx.reply(await conversation.external(({ t }) => t('err-generic')), KEYBOARD_REMOVE); +} diff --git a/apps/bot/src/bot/conversations/index.ts b/apps/bot/src/bot/conversations/index.ts new file mode 100644 index 0000000..cb0d2ef --- /dev/null +++ b/apps/bot/src/bot/conversations/index.ts @@ -0,0 +1 @@ +export * from './add-contact'; diff --git a/apps/bot/src/bot/features/add-contact.ts b/apps/bot/src/bot/features/add-contact.ts index 1c1111b..ac4bf52 100644 --- a/apps/bot/src/bot/features/add-contact.ts +++ b/apps/bot/src/bot/features/add-contact.ts @@ -1,99 +1,11 @@ -/* eslint-disable id-length */ import { type Context } from '@/bot/context'; import { logHandle } from '@/bot/helpers/logging'; -import { KEYBOARD_REMOVE, KEYBOARD_SHARE_BOT, KEYBOARD_SHARE_PHONE } from '@/config/keyboards'; -import { isCustomerMaster } from '@/utils/customer'; -import { isValidPhoneNumber, normalizePhoneNumber } from '@/utils/phone'; -import { type Conversation, createConversation } from '@grammyjs/conversations'; -import { CustomersService } from '@repo/graphql/api/customers'; -import { RegistrationService } from '@repo/graphql/api/registration'; import { Composer } from 'grammy'; const composer = new Composer(); const feature = composer.chatType('private'); -async function addContact(conversation: Conversation, ctx: Context) { - // Проверяем, что пользователь является мастером - const telegramId = ctx.from?.id; - if (!telegramId) { - return ctx.reply(await conversation.external(({ t }) => t('err-generic'))); - } - - const customerService = new CustomersService({ telegramId }); - const { customer } = await customerService.getCustomer({ telegramId }); - - if (!customer) { - return ctx.reply( - await conversation.external(({ t }) => t('msg-need-phone')), - KEYBOARD_SHARE_PHONE, - ); - } - - if (!isCustomerMaster(customer)) { - return ctx.reply(await conversation.external(({ t }) => t('msg-not-master'))); - } - - // Просим отправить контакт клиента - await ctx.reply(await conversation.external(({ t }) => t('msg-send-client-contact'))); - - // Ждем любое сообщение от пользователя - const waitCtx = await conversation.wait(); - - // Проверяем команду отмены - if (waitCtx.message?.text === '/cancel') { - return ctx.reply(await conversation.external(({ t }) => t('msg-cancel'))); - } - - // Проверяем, что отправлен контакт - if (!waitCtx.message?.contact) { - return ctx.reply(await conversation.external(({ t }) => t('msg-send-contact'))); - } - - const { contact } = waitCtx.message; - const name = `${contact.first_name || ''} ${contact.last_name || ''}`.trim(); - const phone = normalizePhoneNumber(contact.phone_number); - - // Проверяем валидность номера телефона - if (!isValidPhoneNumber(phone)) { - return ctx.reply(await conversation.external(({ t }) => t('msg-invalid-phone'))); - } - - try { - // Проверяем, есть ли клиент с таким номером - const { customer: existingCustomer } = await customerService.getCustomer({ phone }); - let documentId = existingCustomer?.documentId; - - // Если клиента нет, создаём нового - if (!documentId) { - const registrationService = new RegistrationService(); - const createCustomerResult = await registrationService.createCustomer({ name, phone }); - - documentId = createCustomerResult?.createCustomer?.documentId; - if (!documentId) throw new Error('Клиент не создан'); - } - - // Добавляем текущего мастера к клиенту - const masters = [customer.documentId]; - await customerService.addMasters({ data: { masters }, documentId }); - - // Отправляем подтверждения и инструкции - await ctx.reply(await conversation.external(({ t }) => t('msg-contact-added', { name }))); - await ctx.reply(await conversation.external(({ t }) => t('msg-contact-forward'))); - await ctx.reply(await conversation.external(({ t }) => t('msg-share-bot')), KEYBOARD_SHARE_BOT); - } catch (error) { - await ctx.reply( - await conversation.external(({ t }) => t('err-with-details', { error: String(error) })), - ); - } finally { - await ctx.reply(await conversation.external(({ t }) => t('commands-list')), KEYBOARD_REMOVE); - } - - return ctx.reply(await conversation.external(({ t }) => t('err-generic')), KEYBOARD_REMOVE); -} - -feature.use(createConversation(addContact)); - feature.command('addcontact', logHandle('command-add-contact'), async (ctx) => { await ctx.conversation.enter('addContact'); }); diff --git a/apps/bot/src/bot/index.ts b/apps/bot/src/bot/index.ts index 649f092..0717f44 100644 --- a/apps/bot/src/bot/index.ts +++ b/apps/bot/src/bot/index.ts @@ -1,4 +1,5 @@ import { type Context } from './context'; +import * as conversations from './conversations'; import * as features from './features'; import { unhandledFeature } from './features/unhandled'; import { errorHandler } from './handlers/errors'; @@ -9,7 +10,7 @@ import { setInfo } from './settings/info'; import { env } from '@/config/env'; import { getRedisInstance } from '@/utils/redis'; import { autoChatAction } from '@grammyjs/auto-chat-action'; -import { conversations } from '@grammyjs/conversations'; +import { createConversation, conversations as grammyConversations } from '@grammyjs/conversations'; import { hydrate } from '@grammyjs/hydrate'; import { limit } from '@grammyjs/ratelimiter'; import { Bot } from 'grammy'; @@ -37,7 +38,11 @@ export function createBot({ token }: Parameters_) { }), ); - bot.use(conversations()); + bot.use(grammyConversations()); + + for (const conversation of Object.values(conversations)) { + bot.use(createConversation(conversation)); + } setInfo(bot); setCommands(bot);