diff --git a/apps/bot/src/index.ts b/apps/bot/src/index.ts index acb5a97..65f28a8 100644 --- a/apps/bot/src/index.ts +++ b/apps/bot/src/index.ts @@ -6,6 +6,7 @@ import { KEYBOARD_REMOVE, KEYBOARD_SHARE_BOT, KEYBOARD_SHARE_PHONE, + MESSAGE_CANCEL, MESSAGE_NOT_MASTER, MESSAGE_SHARE_BOT, MSG_ALREADY_MASTER, @@ -19,13 +20,91 @@ import { MSG_WELCOME, MSG_WELCOME_BACK, } from './message'; +import { isCustomerMaster } from './utils/customer'; import { normalizePhoneNumber } from './utils/phone'; import { CustomersService } from '@repo/graphql/api/customers'; import { Enum_Customer_Role } from '@repo/graphql/types'; -import { Telegraf } from 'telegraf'; +import { Scenes, session, Telegraf, type Context as TelegrafContext } from 'telegraf'; import { message } from 'telegraf/filters'; +import { + type SceneContextScene, + type SceneSession, + type WizardContextWizard, + type WizardSessionData, +} from 'telegraf/typings/scenes'; -const bot = new Telegraf(environment.BOT_TOKEN); +type BotContext = TelegrafContext & { + scene: SceneContextScene; + session: SceneSession; + wizard: WizardContextWizard; +}; + +const bot = new Telegraf(environment.BOT_TOKEN); + +const stage = new Scenes.Stage(); +bot.use(session({ defaultSession: () => ({ __scenes: { cursor: 0, state: {} } }) })); +bot.use(stage.middleware()); + +const addContactScene = new Scenes.WizardScene( + 'add-contact', + async (context) => { + await context.reply(MSG_SEND_CLIENT_CONTACT, { parse_mode: 'HTML' }); + return context.wizard.next(); + }, + async (context) => { + if (!context.from) { + await context.reply('Ошибка: не удалось определить пользователя'); + return context.scene.leave(); + } + + if (context.message && 'text' in context.message && context.message.text === '/cancel') { + await context.reply(MESSAGE_CANCEL + commandsList, { parse_mode: 'HTML' }); + return context.scene.leave(); + } + + if (!('message' in context && context.message && 'contact' in context.message)) { + await context.reply('Пожалуйста, отправьте контакт клиента через кнопку Telegram'); + return; + } + + const telegramId = context.from.id; + const customerService = new CustomersService({ telegramId }); + const { customer } = await customerService.getCustomer({ telegramId }); + if (!customer || !isCustomerMaster(customer)) { + await context.reply(MESSAGE_NOT_MASTER, { parse_mode: 'HTML' }); + return context.scene.leave(); + } + + const { contact } = context.message; + const name = (contact.first_name || '') + ' ' + (contact.last_name || '').trim(); + const phone = normalizePhoneNumber(contact.phone_number); + + try { + const { customer: existingCustomer } = await customerService.getCustomer({ phone }); + + let documentId = existingCustomer?.documentId; + + if (!documentId) { + const createCustomerResult = await customerService.createCustomer({ name, phone }); + documentId = createCustomerResult?.createCustomer?.documentId; + if (!documentId) throw new Error('Customer not created'); + } + + const masters = [customer.documentId]; + await customerService.addMasters({ data: { masters }, documentId }); + await context.reply(MSG_CONTACT_ADDED(name), { parse_mode: 'HTML' }); + await context.reply(MSG_CONTACT_FORWARD, { parse_mode: 'HTML' }); + await context.reply(MESSAGE_SHARE_BOT, { ...KEYBOARD_SHARE_BOT, parse_mode: 'HTML' }); + } catch (error) { + await context.reply(MSG_ERROR(error), { parse_mode: 'HTML' }); + } finally { + await context.reply(commandsList, { ...KEYBOARD_REMOVE, parse_mode: 'HTML' }); + context.scene.leave(); + } + }, +); + +stage.register(addContactScene); bot.start(async (context) => { const telegramId = context.from.id; @@ -49,19 +128,17 @@ bot.command('help', async (context) => { bot.command('addcontact', async (context) => { const telegramId = context.from.id; - const customerService = new CustomersService({ telegramId }); const { customer } = await customerService.getCustomer({ telegramId }); - if (!customer) { return context.reply(MSG_NEED_PHONE, { ...KEYBOARD_SHARE_PHONE, parse_mode: 'HTML' }); } - if (customer.role !== Enum_Customer_Role.Master) { + if (!isCustomerMaster(customer)) { return context.reply(MESSAGE_NOT_MASTER, { parse_mode: 'HTML' }); } - return context.reply(MSG_SEND_CLIENT_CONTACT, { parse_mode: 'HTML' }); + return context.scene.enter('add-contact'); }); bot.command('becomemaster', async (context) => { @@ -74,7 +151,7 @@ bot.command('becomemaster', async (context) => { return context.reply(MSG_NEED_PHONE, { ...KEYBOARD_SHARE_PHONE, parse_mode: 'HTML' }); } - if (customer.role === Enum_Customer_Role.Master) { + if (isCustomerMaster(customer)) { return context.reply(MSG_ALREADY_MASTER, { parse_mode: 'HTML' }); } @@ -100,60 +177,25 @@ bot.command('sharebot', async (context) => { bot.on(message('contact'), async (context) => { const telegramId = context.from.id; - const customerService = new CustomersService({ telegramId }); const { customer } = await customerService.getCustomer({ telegramId }); - const isRegistration = !customer; + if (!customer) { + const { contact } = context.message; + const name = (contact.first_name || '') + ' ' + (contact.last_name || '').trim(); + const phone = normalizePhoneNumber(contact.phone_number); - const { contact } = context.message; - const name = (contact.first_name || '') + ' ' + (contact.last_name || '').trim(); - const phone = normalizePhoneNumber(contact.phone_number); - - if (isRegistration) { const response = await customerService - .createCustomer({ - name, - phone, - telegramId: context.from.id, - }) + .createCustomer({ name, phone, telegramId: context.from.id }) .catch((error) => { context.reply(MSG_ERROR(error), { parse_mode: 'HTML' }); }); - if (response) { return context.reply(MSG_PHONE_SAVED + commandsList, { ...KEYBOARD_REMOVE, parse_mode: 'HTML', }); } - } else { - if (customer.role !== Enum_Customer_Role.Master) { - return context.reply(MESSAGE_NOT_MASTER, { parse_mode: 'HTML' }); - } - - try { - const createCustomerResult = await customerService.createCustomer({ name, phone }); - - const documentId = createCustomerResult?.createCustomer?.documentId; - - if (!documentId) { - throw new Error('Customer not created'); - } - - const masters = [customer.documentId]; - - await customerService.addMasters({ - data: { masters }, - documentId, - }); - - await context.reply(MSG_CONTACT_ADDED(name), { parse_mode: 'HTML' }); - await context.reply(MSG_CONTACT_FORWARD, { parse_mode: 'HTML' }); - await context.reply(MESSAGE_SHARE_BOT, { ...KEYBOARD_SHARE_BOT, parse_mode: 'HTML' }); - } catch (error) { - context.reply(MSG_ERROR(error), { parse_mode: 'HTML' }); - } } }); diff --git a/apps/bot/src/message.ts b/apps/bot/src/message.ts index 51637c2..62ee74a 100644 --- a/apps/bot/src/message.ts +++ b/apps/bot/src/message.ts @@ -43,7 +43,7 @@ export const KEYBOARD_SHARE_BOT = { }; export const MESSAGE_NOT_MASTER = - '⛔️ Только мастер может добавлять контакты.\nСтать мастером можно на странице профиля в приложении или с помощью команды /becomemaster'; + '⛔️ Только мастер может добавлять контакты\nСтать мастером можно на странице профиля в приложении или с помощью команды /becomemaster'; export const MSG_WELCOME = '👋 Добро пожаловать!\nПожалуйста, поделитесь своим номером телефона для регистрации'; @@ -52,26 +52,28 @@ export const MSG_WELCOME_BACK = (name: string) => `👋 С возвращением, ${name}!\nЧтобы воспользоваться сервисом, откройте приложение.\n`; export const MSG_NEED_PHONE = - '📱 Чтобы добавить контакт, сначала поделитесь своим номером телефона.'; + '📱 Чтобы добавить контакт, сначала поделитесь своим номером телефона'; export const MSG_SEND_CLIENT_CONTACT = - '👤 Отправьте контакт клиента, которого вы хотите добавить.'; + '👤 Отправьте контакт клиента, которого вы хотите добавить. \nДля отмены операции используйте команду /cancel'; export const MSG_ALREADY_MASTER = '🎉 Вы уже являетесь мастером!'; -export const MSG_BECOME_MASTER = '🥳 Поздравляем! Теперь вы мастер.'; +export const MSG_BECOME_MASTER = '🥳 Поздравляем! Теперь вы мастер'; export const MSG_ERROR = (error?: unknown) => - `❌ Произошла ошибка.\n${error ? String(error) : ''}`; + `❌ Произошла ошибка\n${error ? String(error) : ''}`; export const MSG_PHONE_SAVED = - '✅ Спасибо! Мы сохранили ваш номер телефона.\nТеперь вы можете открыть приложение или воспользоваться командами бота'; + '✅ Спасибо! Мы сохранили ваш номер телефона\nТеперь вы можете открыть приложение или воспользоваться командами бота'; export const MSG_CONTACT_ADDED = (name: string) => - `✅ Добавили контакт: ${name}\nПригласите пользователя в приложение, чтобы вы могли добавлять записи с этим контактом`; + `✅ Добавили контакт в список ваших клиентов\n\nИмя: ${name}\n\nПригласите клиента в приложение, чтобы вы могли добавлять записи с этим контактом`; export const MSG_CONTACT_FORWARD = - 'Перешлите следующее сообщение, чтобы пользователь мог начать пользоваться ботом ⬇️'; + 'Перешлите пользователю следующее сообщение, чтобы он мог начать пользоваться ботом ⬇️'; export const MESSAGE_SHARE_BOT = '📅 Воспользуйтесь этим ботом для записи к вашему мастеру!\nНажмите кнопку ниже, чтобы начать'; + +export const MESSAGE_CANCEL = '❌ Отменена операции'; diff --git a/apps/bot/src/utils/customer.ts b/apps/bot/src/utils/customer.ts new file mode 100644 index 0000000..499e79c --- /dev/null +++ b/apps/bot/src/utils/customer.ts @@ -0,0 +1,5 @@ +import * as GQL from '@repo/graphql/types'; + +export function isCustomerMaster(customer: GQL.CustomerFieldsFragment) { + return customer?.role === GQL.Enum_Customer_Role.Master; +}