refactor(bot): add comments

This commit is contained in:
vchikalkin 2025-07-03 21:36:44 +03:00
parent d9b054df14
commit 1c295a4b41

View File

@ -35,42 +35,54 @@ import {
type WizardSessionData,
} from 'telegraf/typings/scenes';
// Расширяем контекст бота для работы с сценами и сессиями
type BotContext = TelegrafContext & {
scene: SceneContextScene<BotContext, WizardSessionData>;
session: SceneSession<WizardSessionData>;
wizard: WizardContextWizard<BotContext>;
};
// Создаём экземпляр бота с токеном
const bot = new Telegraf<BotContext>(environment.BOT_TOKEN);
// Создаём менеджер сцен и подключаем сессию
const stage = new Scenes.Stage<BotContext>();
bot.use(session({ defaultSession: () => ({ __scenes: { cursor: 0, state: {} } }) }));
bot.use(stage.middleware());
// Сцена добавления контакта клиента мастером
const addContactScene = new Scenes.WizardScene<BotContext>(
'add-contact',
// Шаг 1: Просим отправить контакт клиента
async (context) => {
await context.reply(MSG_SEND_CLIENT_CONTACT, { parse_mode: 'HTML' });
return context.wizard.next();
},
// Шаг 2: Обрабатываем полученный контакт
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)) {
// Проверяем, что отправлен контакт (через кнопку Telegram)
if (!context.message || !('contact' in context.message)) {
await context.reply('Пожалуйста, отправьте контакт клиента через кнопку Telegram');
return;
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' });
@ -78,23 +90,34 @@ const addContactScene = new Scenes.WizardScene<BotContext>(
}
const { contact } = context.message;
const name = (contact.first_name || '') + ' ' + (contact.last_name || '').trim();
const name = `${contact.first_name || ''} ${contact.last_name || ''}`.trim();
const phone = normalizePhoneNumber(contact.phone_number);
try {
const { customer: existingCustomer } = await customerService.getCustomer({ phone });
// Проверяем валидность номера телефона
if (!isValidPhoneNumber(phone)) {
await context.reply(MESSAGE_INVALID_PHONE, { parse_mode: 'HTML' });
return; // остаёмся в сцене, ждем правильный номер
}
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('Customer not created');
if (!documentId) throw new Error('Клиент не создан');
}
// Добавляем текущего мастера к клиенту
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' });
@ -107,8 +130,10 @@ const addContactScene = new Scenes.WizardScene<BotContext>(
},
);
// Регистрируем сцену
stage.register(addContactScene);
// Команда /start — приветствие и запрос номера, если пользователь новый
bot.start(async (context) => {
const telegramId = context.from.id;
@ -116,37 +141,45 @@ bot.start(async (context) => {
const { customer } = await customerService.getCustomer({ telegramId });
if (customer) {
// Пользователь уже зарегистрирован — приветствуем
return context.reply(MSG_WELCOME_BACK(customer.name) + commandsList, {
...KEYBOARD_REMOVE,
parse_mode: 'HTML',
});
}
// Новый пользователь — просим поделиться номером
return context.reply(MSG_WELCOME, { ...KEYBOARD_SHARE_PHONE, parse_mode: 'HTML' });
});
// Команда /help — список команд
bot.command('help', async (context) => {
return context.reply(commandsList, { ...KEYBOARD_REMOVE, parse_mode: 'HTML' });
});
// Команда /addcontact — начать сцену добавления контакта
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 (!isCustomerMaster(customer)) {
// Нет прав мастера
return context.reply(MESSAGE_NOT_MASTER, { parse_mode: 'HTML' });
}
// Входим в сцену
return context.scene.enter('add-contact');
});
// Команда /becomemaster — запрос статуса мастера
bot.command('becomemaster', async (context) => {
const telegramId = context.from.id;
const customerService = new CustomersService({ telegramId });
const { customer } = await customerService.getCustomer({ telegramId });
@ -158,11 +191,10 @@ bot.command('becomemaster', async (context) => {
return context.reply(MSG_ALREADY_MASTER, { parse_mode: 'HTML' });
}
// Обновляем роль клиента на мастер
const response = await customerService
.updateCustomer({
data: {
role: Enum_Customer_Role.Master,
},
data: { role: Enum_Customer_Role.Master },
})
.catch((error) => {
context.reply(MSG_ERROR(error), { parse_mode: 'HTML' });
@ -173,24 +205,25 @@ bot.command('becomemaster', async (context) => {
}
});
// Команда /sharebot — прислать ссылку на бота
bot.command('sharebot', async (context) => {
await context.reply(MSG_CONTACT_FORWARD, { parse_mode: 'HTML' });
await context.reply(MESSAGE_SHARE_BOT, { ...KEYBOARD_SHARE_BOT, parse_mode: 'HTML' });
});
// Обработка получения контакта от пользователя (регистрация или обновление)
bot.on(message('contact'), async (context) => {
const telegramId = context.from.id;
const { contact } = context.message;
const name = (contact.first_name || '') + ' ' + (contact.last_name || '').trim();
const name = `${contact.first_name || ''} ${contact.last_name || ''}`.trim();
// Проверка наличия номера
// Проверка наличия номера телефона
if (!contact.phone_number) {
return context.reply(MESSAGE_INVALID_PHONE, { parse_mode: 'HTML' });
}
// Нормализация и валидация номера
const phone = normalizePhoneNumber(contact.phone_number);
// Валидация номера
if (!isValidPhoneNumber(phone)) {
return context.reply(MESSAGE_INVALID_PHONE, { parse_mode: 'HTML' });
}
@ -201,13 +234,9 @@ bot.on(message('contact'), async (context) => {
const { customer } = await registrationService.getCustomer({ phone });
if (customer && !customer.telegramId) {
// Пользователь был добавлен ранее мастером — обновляем
// Пользователь добавлен ранее мастером — обновляем данные
await registrationService.updateCustomer({
data: {
active: true,
name,
telegramId,
},
data: { active: true, name, telegramId },
documentId: customer.documentId,
});
@ -217,7 +246,7 @@ bot.on(message('contact'), async (context) => {
});
}
// Иначе — новый пользователь, создаём и активируем
// Новый пользователь — создаём и активируем
const response = await registrationService.createCustomer({ name, phone, telegramId });
const documentId = response?.createCustomer?.documentId;
@ -239,8 +268,9 @@ bot.on(message('contact'), async (context) => {
}
});
// Запуск бота
bot.launch();
// Enable graceful stop
// Корректное завершение работы
process.once('SIGINT', () => bot.stop('SIGINT'));
process.once('SIGTERM', () => bot.stop('SIGTERM'));