init commit
This commit is contained in:
802
src/handlers/callbackHandlers.ts
Normal file
802
src/handlers/callbackHandlers.ts
Normal file
@@ -0,0 +1,802 @@
|
||||
import TelegramBot, { CallbackQuery, InlineKeyboardMarkup } from 'node-telegram-bot-api';
|
||||
import { ProfileService } from '../services/profileService';
|
||||
import { MatchingService } from '../services/matchingService';
|
||||
import { ChatService } from '../services/chatService';
|
||||
import { Profile } from '../models/Profile';
|
||||
import { MessageHandlers } from './messageHandlers';
|
||||
|
||||
export class CallbackHandlers {
|
||||
private bot: TelegramBot;
|
||||
private profileService: ProfileService;
|
||||
private matchingService: MatchingService;
|
||||
private chatService: ChatService;
|
||||
private messageHandlers: MessageHandlers;
|
||||
|
||||
constructor(bot: TelegramBot, messageHandlers: MessageHandlers) {
|
||||
this.bot = bot;
|
||||
this.profileService = new ProfileService();
|
||||
this.matchingService = new MatchingService();
|
||||
this.chatService = new ChatService();
|
||||
this.messageHandlers = messageHandlers;
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.bot.on('callback_query', (query) => this.handleCallback(query));
|
||||
}
|
||||
|
||||
async handleCallback(query: CallbackQuery): Promise<void> {
|
||||
if (!query.data || !query.from || !query.message) return;
|
||||
|
||||
const telegramId = query.from.id.toString();
|
||||
const chatId = query.message.chat.id;
|
||||
const data = query.data;
|
||||
|
||||
try {
|
||||
// Основные действия профиля
|
||||
if (data === 'create_profile') {
|
||||
await this.handleCreateProfile(chatId, telegramId);
|
||||
} else if (data.startsWith('gender_')) {
|
||||
const gender = data.replace('gender_', '');
|
||||
await this.handleGenderSelection(chatId, telegramId, gender);
|
||||
} else if (data === 'view_my_profile') {
|
||||
await this.handleViewMyProfile(chatId, telegramId);
|
||||
} else if (data === 'edit_profile') {
|
||||
await this.handleEditProfile(chatId, telegramId);
|
||||
} else if (data === 'manage_photos') {
|
||||
await this.handleManagePhotos(chatId, telegramId);
|
||||
}
|
||||
|
||||
// Просмотр анкет и свайпы
|
||||
else if (data === 'start_browsing') {
|
||||
await this.handleStartBrowsing(chatId, telegramId);
|
||||
} else if (data === 'next_candidate') {
|
||||
await this.handleNextCandidate(chatId, telegramId);
|
||||
} else if (data.startsWith('like_')) {
|
||||
const targetUserId = data.replace('like_', '');
|
||||
await this.handleLike(chatId, telegramId, targetUserId);
|
||||
} else if (data.startsWith('dislike_')) {
|
||||
const targetUserId = data.replace('dislike_', '');
|
||||
await this.handleDislike(chatId, telegramId, targetUserId);
|
||||
} else if (data.startsWith('superlike_')) {
|
||||
const targetUserId = data.replace('superlike_', '');
|
||||
await this.handleSuperlike(chatId, telegramId, targetUserId);
|
||||
} else if (data.startsWith('view_profile_')) {
|
||||
const targetUserId = data.replace('view_profile_', '');
|
||||
await this.handleViewProfile(chatId, telegramId, targetUserId);
|
||||
} else if (data.startsWith('more_photos_')) {
|
||||
const targetUserId = data.replace('more_photos_', '');
|
||||
await this.handleMorePhotos(chatId, telegramId, targetUserId);
|
||||
}
|
||||
|
||||
// Матчи и чаты
|
||||
else if (data === 'view_matches') {
|
||||
await this.handleViewMatches(chatId, telegramId);
|
||||
} else if (data === 'open_chats') {
|
||||
await this.handleOpenChats(chatId, telegramId);
|
||||
} else if (data.startsWith('chat_')) {
|
||||
const matchId = data.replace('chat_', '');
|
||||
await this.handleOpenChat(chatId, telegramId, matchId);
|
||||
} else if (data.startsWith('send_message_')) {
|
||||
const matchId = data.replace('send_message_', '');
|
||||
await this.handleSendMessage(chatId, telegramId, matchId);
|
||||
} else if (data.startsWith('view_chat_profile_')) {
|
||||
const matchId = data.replace('view_chat_profile_', '');
|
||||
await this.handleViewChatProfile(chatId, telegramId, matchId);
|
||||
} else if (data.startsWith('unmatch_')) {
|
||||
const matchId = data.replace('unmatch_', '');
|
||||
await this.handleUnmatch(chatId, telegramId, matchId);
|
||||
} else if (data.startsWith('confirm_unmatch_')) {
|
||||
const matchId = data.replace('confirm_unmatch_', '');
|
||||
await this.handleConfirmUnmatch(chatId, telegramId, matchId);
|
||||
}
|
||||
|
||||
// Настройки
|
||||
else if (data === 'settings') {
|
||||
await this.handleSettings(chatId, telegramId);
|
||||
} else if (data === 'search_settings') {
|
||||
await this.handleSearchSettings(chatId, telegramId);
|
||||
} else if (data === 'notification_settings') {
|
||||
await this.handleNotificationSettings(chatId, telegramId);
|
||||
}
|
||||
|
||||
// Информация
|
||||
else if (data === 'how_it_works') {
|
||||
await this.handleHowItWorks(chatId);
|
||||
} else if (data === 'back_to_browsing') {
|
||||
await this.handleStartBrowsing(chatId, telegramId);
|
||||
}
|
||||
|
||||
else {
|
||||
await this.bot.answerCallbackQuery(query.id, {
|
||||
text: 'Функция в разработке!',
|
||||
show_alert: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.bot.answerCallbackQuery(query.id);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Callback handler error:', error);
|
||||
await this.bot.answerCallbackQuery(query.id, {
|
||||
text: 'Произошла ошибка. Попробуйте еще раз.',
|
||||
show_alert: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Создание профиля
|
||||
async handleCreateProfile(chatId: number, telegramId: string): Promise<void> {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '👨 Мужской', callback_data: 'gender_male' }],
|
||||
[{ text: '👩 Женский', callback_data: 'gender_female' }],
|
||||
[{ text: '🔀 Другой', callback_data: 'gender_other' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'👋 Давайте создадим ваш профиль!\n\n' +
|
||||
'🚹🚺 Сначала выберите ваш пол:',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
|
||||
// Выбор пола
|
||||
async handleGenderSelection(chatId: number, telegramId: string, gender: string): Promise<void> {
|
||||
this.messageHandlers.startProfileCreation(telegramId, gender);
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'👍 Отлично!\n\n📝 Теперь напишите ваше имя:'
|
||||
);
|
||||
}
|
||||
|
||||
// Просмотр собственного профиля
|
||||
async handleViewMyProfile(chatId: number, telegramId: string): Promise<void> {
|
||||
const profile = await this.profileService.getProfileByTelegramId(telegramId);
|
||||
|
||||
if (!profile) {
|
||||
await this.bot.sendMessage(chatId, '❌ Профиль не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showProfile(chatId, profile, true);
|
||||
}
|
||||
|
||||
// Редактирование профиля
|
||||
async handleEditProfile(chatId: number, telegramId: string): Promise<void> {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '📝 Имя', callback_data: 'edit_name' },
|
||||
{ text: '📅 Возраст', callback_data: 'edit_age' }
|
||||
],
|
||||
[
|
||||
{ text: '📍 Город', callback_data: 'edit_city' },
|
||||
{ text: '💼 Работа', callback_data: 'edit_job' }
|
||||
],
|
||||
[
|
||||
{ text: '📖 О себе', callback_data: 'edit_bio' },
|
||||
{ text: '🎯 Интересы', callback_data: 'edit_interests' }
|
||||
],
|
||||
[{ text: '👈 Назад к профилю', callback_data: 'view_my_profile' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'✏️ Что хотите изменить в профиле?',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
|
||||
// Управление фотографиями
|
||||
async handleManagePhotos(chatId: number, telegramId: string): Promise<void> {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '📷 Добавить фото', callback_data: 'add_photo' },
|
||||
{ text: '🗑 Удалить фото', callback_data: 'delete_photo' }
|
||||
],
|
||||
[
|
||||
{ text: '⭐ Сделать главным', callback_data: 'set_main_photo' },
|
||||
{ text: '🔄 Изменить порядок', callback_data: 'reorder_photos' }
|
||||
],
|
||||
[{ text: '👈 Назад к профилю', callback_data: 'view_my_profile' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'📸 Управление фотографиями\n\nВыберите действие:',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
|
||||
// Начать просмотр анкет
|
||||
async handleStartBrowsing(chatId: number, telegramId: string): Promise<void> {
|
||||
const profile = await this.profileService.getProfileByTelegramId(telegramId);
|
||||
|
||||
if (!profile) {
|
||||
await this.bot.sendMessage(chatId, '❌ Сначала создайте профиль!');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showNextCandidate(chatId, telegramId);
|
||||
}
|
||||
|
||||
// Следующий кандидат
|
||||
async handleNextCandidate(chatId: number, telegramId: string): Promise<void> {
|
||||
await this.showNextCandidate(chatId, telegramId);
|
||||
}
|
||||
|
||||
// Лайк
|
||||
async handleLike(chatId: number, telegramId: string, targetUserId: string): Promise<void> {
|
||||
try {
|
||||
const result = await this.matchingService.performSwipe(telegramId, targetUserId, 'like');
|
||||
|
||||
if (result.isMatch) {
|
||||
// Это матч!
|
||||
const targetProfile = await this.profileService.getProfileByUserId(targetUserId);
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '💬 Написать сообщение', callback_data: 'chat_' + targetUserId },
|
||||
{ text: '👤 Посмотреть профиль', callback_data: 'view_profile_' + targetUserId }
|
||||
],
|
||||
[{ text: '🔍 Продолжить поиск', callback_data: 'next_candidate' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'🎉 ЭТО МАТЧ! 💕\n\n' +
|
||||
'Вы понравились друг другу с ' + (targetProfile?.name || 'этим пользователем') + '!\n\n' +
|
||||
'Теперь вы можете начать общение!',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
} else {
|
||||
await this.bot.sendMessage(chatId, '👍 Лайк отправлен!');
|
||||
await this.showNextCandidate(chatId, telegramId);
|
||||
}
|
||||
} catch (error) {
|
||||
await this.bot.sendMessage(chatId, '❌ Ошибка при отправке лайка');
|
||||
console.error('Like error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Дизлайк
|
||||
async handleDislike(chatId: number, telegramId: string, targetUserId: string): Promise<void> {
|
||||
try {
|
||||
await this.matchingService.performSwipe(telegramId, targetUserId, 'pass');
|
||||
await this.showNextCandidate(chatId, telegramId);
|
||||
} catch (error) {
|
||||
await this.bot.sendMessage(chatId, '❌ Ошибка при отправке дизлайка');
|
||||
console.error('Dislike error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Супер лайк
|
||||
async handleSuperlike(chatId: number, telegramId: string, targetUserId: string): Promise<void> {
|
||||
try {
|
||||
const result = await this.matchingService.performSwipe(telegramId, targetUserId, 'superlike');
|
||||
|
||||
if (result.isMatch) {
|
||||
const targetProfile = await this.profileService.getProfileByUserId(targetUserId);
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '💬 Написать сообщение', callback_data: 'chat_' + targetUserId },
|
||||
{ text: '👤 Посмотреть профиль', callback_data: 'view_profile_' + targetUserId }
|
||||
],
|
||||
[{ text: '🔍 Продолжить поиск', callback_data: 'next_candidate' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'💖 СУПЕР МАТЧ! ⭐\n\n' +
|
||||
'Ваш супер лайк произвел впечатление на ' + (targetProfile?.name || 'этого пользователя') + '!\n\n' +
|
||||
'Начните общение первыми!',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
} else {
|
||||
await this.bot.sendMessage(chatId, '💖 Супер лайк отправлен!');
|
||||
await this.showNextCandidate(chatId, telegramId);
|
||||
}
|
||||
} catch (error) {
|
||||
await this.bot.sendMessage(chatId, '❌ Ошибка при отправке супер лайка');
|
||||
console.error('Superlike error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Просмотр профиля кандидата
|
||||
async handleViewProfile(chatId: number, telegramId: string, targetUserId: string): Promise<void> {
|
||||
const targetProfile = await this.profileService.getProfileByUserId(targetUserId);
|
||||
|
||||
if (!targetProfile) {
|
||||
await this.bot.sendMessage(chatId, '❌ Профиль не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showProfile(chatId, targetProfile, false, telegramId);
|
||||
}
|
||||
|
||||
// Показать больше фотографий
|
||||
async handleMorePhotos(chatId: number, telegramId: string, targetUserId: string): Promise<void> {
|
||||
const targetProfile = await this.profileService.getProfileByUserId(targetUserId);
|
||||
|
||||
if (!targetProfile || targetProfile.photos.length <= 1) {
|
||||
await this.bot.sendMessage(chatId, '📷 У пользователя нет дополнительных фотографий');
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 1; i < targetProfile.photos.length; i++) {
|
||||
const photoFileId = targetProfile.photos[i];
|
||||
await this.bot.sendPhoto(chatId, photoFileId);
|
||||
}
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '👎 Не нравится', callback_data: 'dislike_' + targetUserId },
|
||||
{ text: '💖 Супер лайк', callback_data: 'superlike_' + targetUserId },
|
||||
{ text: '👍 Нравится', callback_data: 'like_' + targetUserId }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'📸 Все фотографии просмотрены!\n\nВаше решение?',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
|
||||
// Просмотр матчей
|
||||
async handleViewMatches(chatId: number, telegramId: string): Promise<void> {
|
||||
const matches = await this.matchingService.getUserMatches(telegramId);
|
||||
|
||||
if (matches.length === 0) {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '🔍 Начать поиск', callback_data: 'start_browsing' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'💔 У вас пока нет матчей\n\n' +
|
||||
'Попробуйте просмотреть больше анкет!',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let matchText = 'Ваши матчи (' + matches.length + '):\n\n';
|
||||
|
||||
for (const match of matches) {
|
||||
const otherUserId = match.userId1 === telegramId ? match.userId2 : match.userId1;
|
||||
const otherProfile = await this.profileService.getProfileByUserId(otherUserId);
|
||||
|
||||
if (otherProfile) {
|
||||
matchText += '💖 ' + otherProfile.name + ', ' + otherProfile.age + '\n';
|
||||
matchText += '📍 ' + (otherProfile.city || 'Не указан') + '\n\n';
|
||||
}
|
||||
}
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '💬 Открыть чаты', callback_data: 'open_chats' }],
|
||||
[{ text: '🔍 Найти еще', callback_data: 'start_browsing' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(chatId, matchText, { reply_markup: keyboard });
|
||||
}
|
||||
|
||||
// Открыть чаты
|
||||
// Открыть список чатов
|
||||
async handleOpenChats(chatId: number, telegramId: string): Promise<void> {
|
||||
const chats = await this.chatService.getUserChats(telegramId);
|
||||
|
||||
if (chats.length === 0) {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '🔍 Найти матчи', callback_data: 'start_browsing' }],
|
||||
[{ text: '💕 Мои матчи', callback_data: 'view_matches' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'💬 У вас пока нет активных чатов\n\n' +
|
||||
'Начните просматривать анкеты и получите первые матчи!',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let messageText = '💬 Ваши чаты:\n\n';
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: []
|
||||
};
|
||||
|
||||
for (const chat of chats.slice(0, 10)) { // Показываем только первые 10 чатов
|
||||
const unreadBadge = chat.unreadCount > 0 ? ` (${chat.unreadCount})` : '';
|
||||
const lastMessagePreview = chat.lastMessage
|
||||
? (chat.lastMessage.length > 30
|
||||
? chat.lastMessage.substring(0, 30) + '...'
|
||||
: chat.lastMessage)
|
||||
: 'Новый матч';
|
||||
|
||||
messageText += `💕 ${chat.otherUserName}${unreadBadge}\n`;
|
||||
messageText += `💬 ${lastMessagePreview}\n\n`;
|
||||
|
||||
keyboard.inline_keyboard.push([
|
||||
{ text: `💬 ${chat.otherUserName}${unreadBadge}`, callback_data: `chat_${chat.matchId}` }
|
||||
]);
|
||||
}
|
||||
|
||||
if (chats.length > 10) {
|
||||
messageText += `...и еще ${chats.length - 10} чатов`;
|
||||
}
|
||||
|
||||
keyboard.inline_keyboard.push([
|
||||
{ text: '🔍 Найти еще', callback_data: 'start_browsing' },
|
||||
{ text: '💕 Матчи', callback_data: 'view_matches' }
|
||||
]);
|
||||
|
||||
await this.bot.sendMessage(chatId, messageText, { reply_markup: keyboard });
|
||||
}
|
||||
|
||||
// Открыть конкретный чат
|
||||
async handleOpenChat(chatId: number, telegramId: string, matchId: string): Promise<void> {
|
||||
const matchInfo = await this.chatService.getMatchInfo(matchId, telegramId);
|
||||
|
||||
if (!matchInfo) {
|
||||
await this.bot.sendMessage(chatId, '❌ Чат не найден или недоступен');
|
||||
return;
|
||||
}
|
||||
|
||||
// Отмечаем сообщения как прочитанные
|
||||
await this.chatService.markMessagesAsRead(matchId, telegramId);
|
||||
|
||||
// Получаем последние сообщения
|
||||
const messages = await this.chatService.getChatMessages(matchId, 10);
|
||||
|
||||
let chatText = `💬 Чат с ${matchInfo.otherUserProfile?.name}\n\n`;
|
||||
|
||||
if (messages.length === 0) {
|
||||
chatText += '📝 Начните общение! Напишите первое сообщение.\n\n';
|
||||
} else {
|
||||
chatText += '📝 Последние сообщения:\n\n';
|
||||
|
||||
for (const message of messages.slice(-5)) { // Показываем последние 5 сообщений
|
||||
const currentUserId = await this.profileService.getUserIdByTelegramId(telegramId);
|
||||
const isFromMe = message.senderId === currentUserId;
|
||||
const sender = isFromMe ? 'Вы' : matchInfo.otherUserProfile?.name;
|
||||
const time = message.createdAt.toLocaleTimeString('ru-RU', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
chatText += `${sender} (${time}):\n${message.content}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '✍️ Написать сообщение', callback_data: `send_message_${matchId}` }
|
||||
],
|
||||
[
|
||||
{ text: '👤 Профиль', callback_data: `view_chat_profile_${matchId}` },
|
||||
{ text: '💔 Удалить матч', callback_data: `unmatch_${matchId}` }
|
||||
],
|
||||
[
|
||||
{ text: '← Назад к чатам', callback_data: 'open_chats' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(chatId, chatText, { reply_markup: keyboard });
|
||||
}
|
||||
|
||||
// Отправить сообщение
|
||||
async handleSendMessage(chatId: number, telegramId: string, matchId: string): Promise<void> {
|
||||
const matchInfo = await this.chatService.getMatchInfo(matchId, telegramId);
|
||||
|
||||
if (!matchInfo) {
|
||||
await this.bot.sendMessage(chatId, '❌ Чат не найден или недоступен');
|
||||
return;
|
||||
}
|
||||
|
||||
// Устанавливаем состояние ожидания сообщения
|
||||
this.messageHandlers.setWaitingForMessage(telegramId, matchId);
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '❌ Отмена', callback_data: `chat_${matchId}` }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
`✍️ Напишите сообщение для ${matchInfo.otherUserProfile?.name}:\n\n` +
|
||||
'💡 Просто отправьте текст в этот чат',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
|
||||
// Просмотр профиля в чате
|
||||
async handleViewChatProfile(chatId: number, telegramId: string, matchId: string): Promise<void> {
|
||||
const matchInfo = await this.chatService.getMatchInfo(matchId, telegramId);
|
||||
|
||||
if (!matchInfo || !matchInfo.otherUserProfile) {
|
||||
await this.bot.sendMessage(chatId, '❌ Профиль не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showProfile(chatId, matchInfo.otherUserProfile, false, telegramId);
|
||||
}
|
||||
|
||||
// Удалить матч (размэтчиться)
|
||||
async handleUnmatch(chatId: number, telegramId: string, matchId: string): Promise<void> {
|
||||
const matchInfo = await this.chatService.getMatchInfo(matchId, telegramId);
|
||||
|
||||
if (!matchInfo) {
|
||||
await this.bot.sendMessage(chatId, '❌ Матч не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '✅ Да, удалить', callback_data: `confirm_unmatch_${matchId}` },
|
||||
{ text: '❌ Отмена', callback_data: `chat_${matchId}` }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
`💔 Вы уверены, что хотите удалить матч с ${matchInfo.otherUserProfile?.name}?\n\n` +
|
||||
'⚠️ Это действие нельзя отменить. Вся переписка будет удалена.',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
|
||||
// Подтвердить удаление матча
|
||||
async handleConfirmUnmatch(chatId: number, telegramId: string, matchId: string): Promise<void> {
|
||||
const success = await this.chatService.unmatch(matchId, telegramId);
|
||||
|
||||
if (success) {
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'💔 Матч удален\n\n' +
|
||||
'Вы больше не увидите этого пользователя в своих матчах.'
|
||||
);
|
||||
|
||||
// Возвращаемся к списку чатов
|
||||
setTimeout(() => {
|
||||
this.handleOpenChats(chatId, telegramId);
|
||||
}, 2000);
|
||||
} else {
|
||||
await this.bot.sendMessage(chatId, '❌ Не удалось удалить матч. Попробуйте еще раз.');
|
||||
}
|
||||
}
|
||||
|
||||
// Настройки
|
||||
async handleSettings(chatId: number, telegramId: string): Promise<void> {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '🔍 Настройки поиска', callback_data: 'search_settings' },
|
||||
{ text: '🔔 Уведомления', callback_data: 'notification_settings' }
|
||||
],
|
||||
[
|
||||
{ text: '🚫 Скрыть профиль', callback_data: 'hide_profile' },
|
||||
{ text: '🗑 Удалить профиль', callback_data: 'delete_profile' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'⚙️ Настройки профиля\n\nВыберите что хотите изменить:',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
|
||||
// Настройки поиска
|
||||
async handleSearchSettings(chatId: number, telegramId: string): Promise<void> {
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'🔍 Настройки поиска будут доступны в следующем обновлении!'
|
||||
);
|
||||
}
|
||||
|
||||
// Настройки уведомлений
|
||||
async handleNotificationSettings(chatId: number, telegramId: string): Promise<void> {
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'🔔 Настройки уведомлений будут доступны в следующем обновлении!'
|
||||
);
|
||||
}
|
||||
|
||||
// Как это работает
|
||||
async handleHowItWorks(chatId: number): Promise<void> {
|
||||
const helpText =
|
||||
'🎯 Как работает Telegram Tinder Bot?\n\n' +
|
||||
'1️⃣ Создайте профиль\n' +
|
||||
' • Добавьте фото и описание\n' +
|
||||
' • Укажите ваши предпочтения\n\n' +
|
||||
'2️⃣ Просматривайте анкеты\n' +
|
||||
' • Ставьте лайки понравившимся\n' +
|
||||
' • Используйте супер лайки для особых случаев\n\n' +
|
||||
'3️⃣ Получайте матчи\n' +
|
||||
' • Когда ваш лайк взаимен - это матч!\n' +
|
||||
' • Начинайте общение\n\n' +
|
||||
'4️⃣ Общайтесь и знакомьтесь\n' +
|
||||
' • Находите общие интересы\n' +
|
||||
' • Договаривайтесь о встрече\n\n' +
|
||||
'<27><> Советы:\n' +
|
||||
'• Используйте качественные фото\n' +
|
||||
'• Напишите интересное описание\n' +
|
||||
'• Будьте вежливы в общении\n\n' +
|
||||
'❤️ Удачи в поиске любви!';
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '🚀 Создать профиль', callback_data: 'create_profile' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(chatId, helpText, { reply_markup: keyboard });
|
||||
}
|
||||
|
||||
// Вспомогательные методы
|
||||
async showProfile(chatId: number, profile: Profile, isOwner: boolean = false, viewerId?: string): Promise<void> {
|
||||
const mainPhotoFileId = profile.photos[0]; // Первое фото - главное
|
||||
|
||||
let profileText = '👤 ' + profile.name + ', ' + profile.age + '\n';
|
||||
profileText += '📍 ' + (profile.city || 'Не указан') + '\n';
|
||||
if (profile.job) profileText += '💼 ' + profile.job + '\n';
|
||||
if (profile.education) profileText += '🎓 ' + profile.education + '\n';
|
||||
if (profile.height) profileText += '📏 ' + profile.height + ' см\n';
|
||||
profileText += '\n📝 ' + (profile.bio || 'Описание не указано') + '\n';
|
||||
|
||||
if (profile.interests.length > 0) {
|
||||
profileText += '\n🎯 Интересы: ' + profile.interests.join(', ');
|
||||
}
|
||||
|
||||
let keyboard: InlineKeyboardMarkup;
|
||||
|
||||
if (isOwner) {
|
||||
keyboard = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '✏️ Редактировать', callback_data: 'edit_profile' },
|
||||
{ text: '📸 Фото', callback_data: 'manage_photos' }
|
||||
],
|
||||
[{ text: '🔍 Начать поиск', callback_data: 'start_browsing' }]
|
||||
]
|
||||
};
|
||||
} else {
|
||||
keyboard = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '👎 Не нравится', callback_data: 'dislike_' + profile.userId },
|
||||
{ text: '💖 Супер лайк', callback_data: 'superlike_' + profile.userId },
|
||||
{ text: '👍 Нравится', callback_data: 'like_' + profile.userId }
|
||||
],
|
||||
[{ text: '🔍 Продолжить поиск', callback_data: 'next_candidate' }]
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// Проверяем, есть ли валидное фото (file_id или URL)
|
||||
const hasValidPhoto = mainPhotoFileId &&
|
||||
(mainPhotoFileId.startsWith('http') ||
|
||||
mainPhotoFileId.startsWith('AgAC') ||
|
||||
mainPhotoFileId.length > 20); // file_id обычно длинные
|
||||
|
||||
if (hasValidPhoto) {
|
||||
try {
|
||||
await this.bot.sendPhoto(chatId, mainPhotoFileId, {
|
||||
caption: profileText,
|
||||
reply_markup: keyboard
|
||||
});
|
||||
} catch (error) {
|
||||
// Если не удалось отправить фото, отправляем текст
|
||||
await this.bot.sendMessage(chatId, '🖼 Фото недоступно\n\n' + profileText, {
|
||||
reply_markup: keyboard
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Отправляем как текстовое сообщение
|
||||
await this.bot.sendMessage(chatId, profileText, {
|
||||
reply_markup: keyboard
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async showNextCandidate(chatId: number, telegramId: string): Promise<void> {
|
||||
const candidate = await this.matchingService.getNextCandidate(telegramId);
|
||||
|
||||
if (!candidate) {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '🔄 Попробовать еще раз', callback_data: 'start_browsing' }],
|
||||
[{ text: '💕 Мои матчи', callback_data: 'view_matches' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'🎉 Вы просмотрели всех доступных кандидатов!\n\n' +
|
||||
'⏰ Попробуйте позже - возможно появятся новые анкеты!',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const candidatePhotoFileId = candidate.photos[0]; // Первое фото - главное
|
||||
|
||||
let candidateText = candidate.name + ', ' + candidate.age + '\n';
|
||||
candidateText += '📍 ' + (candidate.city || 'Не указан') + '\n';
|
||||
if (candidate.job) candidateText += '💼 ' + candidate.job + '\n';
|
||||
if (candidate.education) candidateText += '🎓 ' + candidate.education + '\n';
|
||||
if (candidate.height) candidateText += '<27><> ' + candidate.height + ' см\n';
|
||||
candidateText += '\n📝 ' + (candidate.bio || 'Описание отсутствует') + '\n';
|
||||
|
||||
if (candidate.interests.length > 0) {
|
||||
candidateText += '\n🎯 Интересы: ' + candidate.interests.join(', ');
|
||||
}
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '👎 Не нравится', callback_data: 'dislike_' + candidate.userId },
|
||||
{ text: '💖 Супер лайк', callback_data: 'superlike_' + candidate.userId },
|
||||
{ text: '👍 Нравится', callback_data: 'like_' + candidate.userId }
|
||||
],
|
||||
[
|
||||
{ text: '👤 Профиль', callback_data: 'view_profile_' + candidate.userId },
|
||||
{ text: '📸 Еще фото', callback_data: 'more_photos_' + candidate.userId }
|
||||
],
|
||||
[{ text: '⏭ Следующий', callback_data: 'next_candidate' }]
|
||||
]
|
||||
};
|
||||
|
||||
// Проверяем, есть ли валидное фото (file_id или URL)
|
||||
const hasValidPhoto = candidatePhotoFileId &&
|
||||
(candidatePhotoFileId.startsWith('http') ||
|
||||
candidatePhotoFileId.startsWith('AgAC') ||
|
||||
candidatePhotoFileId.length > 20); // file_id обычно длинные
|
||||
|
||||
if (hasValidPhoto) {
|
||||
try {
|
||||
await this.bot.sendPhoto(chatId, candidatePhotoFileId, {
|
||||
caption: candidateText,
|
||||
reply_markup: keyboard
|
||||
});
|
||||
} catch (error) {
|
||||
// Если не удалось отправить фото, отправляем текст
|
||||
await this.bot.sendMessage(chatId, '🖼 Фото недоступно\n\n' + candidateText, {
|
||||
reply_markup: keyboard
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Отправляем как текстовое сообщение
|
||||
await this.bot.sendMessage(chatId, '📝 ' + candidateText, {
|
||||
reply_markup: keyboard
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
302
src/handlers/commandHandlers.ts
Normal file
302
src/handlers/commandHandlers.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
import TelegramBot, { Message, InlineKeyboardMarkup } from 'node-telegram-bot-api';
|
||||
import { ProfileService } from '../services/profileService';
|
||||
import { MatchingService } from '../services/matchingService';
|
||||
import { Profile } from '../models/Profile';
|
||||
|
||||
export class CommandHandlers {
|
||||
private bot: TelegramBot;
|
||||
private profileService: ProfileService;
|
||||
private matchingService: MatchingService;
|
||||
|
||||
constructor(bot: TelegramBot) {
|
||||
this.bot = bot;
|
||||
this.profileService = new ProfileService();
|
||||
this.matchingService = new MatchingService();
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.bot.onText(/\/start/, (msg: Message) => this.handleStart(msg));
|
||||
this.bot.onText(/\/help/, (msg: Message) => this.handleHelp(msg));
|
||||
this.bot.onText(/\/profile/, (msg: Message) => this.handleProfile(msg));
|
||||
this.bot.onText(/\/browse/, (msg: Message) => this.handleBrowse(msg));
|
||||
this.bot.onText(/\/matches/, (msg: Message) => this.handleMatches(msg));
|
||||
this.bot.onText(/\/settings/, (msg: Message) => this.handleSettings(msg));
|
||||
this.bot.onText(/\/create_profile/, (msg: Message) => this.handleCreateProfile(msg));
|
||||
}
|
||||
|
||||
async handleStart(msg: Message): Promise<void> {
|
||||
const userId = msg.from?.id.toString();
|
||||
if (!userId) return;
|
||||
|
||||
// Проверяем есть ли у пользователя профиль
|
||||
const existingProfile = await this.profileService.getProfileByTelegramId(userId);
|
||||
|
||||
if (existingProfile) {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '👤 Мой профиль', callback_data: 'view_my_profile' },
|
||||
{ text: '🔍 Просмотр анкет', callback_data: 'start_browsing' }
|
||||
],
|
||||
[
|
||||
{ text: '💕 Мои матчи', callback_data: 'view_matches' },
|
||||
{ text: '⚙️ Настройки', callback_data: 'settings' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
`🎉 С возвращением, ${existingProfile.name}!\n\n` +
|
||||
`💖 Telegram Tinder Bot готов к работе!\n\n` +
|
||||
`Что хотите сделать?`,
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
} else {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '<27> Создать профиль', callback_data: 'create_profile' }],
|
||||
[{ text: 'ℹ️ Как это работает?', callback_data: 'how_it_works' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
`🎉 Добро пожаловать в Telegram Tinder Bot!\n\n` +
|
||||
`💕 Здесь вы можете найти свою вторую половинку!\n\n` +
|
||||
`Для начала создайте свой профиль:`,
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async handleHelp(msg: Message): Promise<void> {
|
||||
const helpText = `
|
||||
🤖 Telegram Tinder Bot - Справка
|
||||
|
||||
📋 Доступные команды:
|
||||
/start - Главное меню
|
||||
/profile - Управление профилем
|
||||
/browse - Просмотр анкет
|
||||
/matches - Ваши матчи
|
||||
/settings - Настройки
|
||||
/help - Эта справка
|
||||
|
||||
<EFBFBD> Как использовать:
|
||||
1. Создайте профиль с фото и описанием
|
||||
2. Просматривайте анкеты других пользователей
|
||||
3. Ставьте лайки понравившимся
|
||||
4. Общайтесь с взаимными матчами!
|
||||
|
||||
❤️ Удачи в поиске любви!
|
||||
`;
|
||||
|
||||
await this.bot.sendMessage(msg.chat.id, helpText.trim());
|
||||
}
|
||||
|
||||
async handleProfile(msg: Message): Promise<void> {
|
||||
const userId = msg.from?.id.toString();
|
||||
if (!userId) return;
|
||||
|
||||
const profile = await this.profileService.getProfileByTelegramId(userId);
|
||||
|
||||
if (!profile) {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '🚀 Создать профиль', callback_data: 'create_profile' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'❌ У вас пока нет профиля.\nСоздайте его для начала использования бота!',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Показываем профиль пользователя
|
||||
await this.showUserProfile(msg.chat.id, profile, true);
|
||||
}
|
||||
|
||||
async handleBrowse(msg: Message): Promise<void> {
|
||||
const userId = msg.from?.id.toString();
|
||||
if (!userId) return;
|
||||
|
||||
const profile = await this.profileService.getProfileByTelegramId(userId);
|
||||
|
||||
if (!profile) {
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'❌ Сначала создайте профиль!\nИспользуйте команду /start'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.showNextCandidate(msg.chat.id, userId);
|
||||
}
|
||||
|
||||
async handleMatches(msg: Message): Promise<void> {
|
||||
const userId = msg.from?.id.toString();
|
||||
if (!userId) return;
|
||||
|
||||
// Получаем матчи пользователя
|
||||
const matches = await this.matchingService.getUserMatches(userId);
|
||||
|
||||
if (matches.length === 0) {
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'<27> У вас пока нет матчей.\n\n' +
|
||||
'🔍 Попробуйте просмотреть больше анкет!\n' +
|
||||
'Используйте /browse для поиска.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let matchText = `💕 Ваши матчи (${matches.length}):\n\n`;
|
||||
|
||||
for (const match of matches) {
|
||||
const otherUserId = match.userId1 === userId ? match.userId2 : match.userId1;
|
||||
const otherProfile = await this.profileService.getProfileByUserId(otherUserId);
|
||||
|
||||
if (otherProfile) {
|
||||
matchText += `💖 ${otherProfile.name}, ${otherProfile.age}\n`;
|
||||
matchText += `📍 ${otherProfile.city || 'Не указан'}\n`;
|
||||
matchText += `💌 Матч: ${new Date(match.createdAt).toLocaleDateString()}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '💬 Открыть чаты', callback_data: 'open_chats' }],
|
||||
[{ text: '🔍 Найти еще', callback_data: 'start_browsing' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(msg.chat.id, matchText, { reply_markup: keyboard });
|
||||
}
|
||||
|
||||
async handleSettings(msg: Message): Promise<void> {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '🔍 Настройки поиска', callback_data: 'search_settings' },
|
||||
{ text: '🔔 Уведомления', callback_data: 'notification_settings' }
|
||||
],
|
||||
[
|
||||
{ text: '🚫 Скрыть профиль', callback_data: 'hide_profile' },
|
||||
{ text: '🗑 Удалить профиль', callback_data: 'delete_profile' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'⚙️ Настройки профиля\n\nВыберите что хотите изменить:',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
|
||||
async handleCreateProfile(msg: Message): Promise<void> {
|
||||
const userId = msg.from?.id.toString();
|
||||
if (!userId) return;
|
||||
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'👋 Давайте создадим ваш профиль!\n\n' +
|
||||
'📝 Сначала напишите ваше имя:'
|
||||
);
|
||||
|
||||
// Устанавливаем состояние ожидания имени
|
||||
// Это будет обрабатываться в messageHandlers
|
||||
}
|
||||
|
||||
// Вспомогательные методы
|
||||
async showUserProfile(chatId: number, profile: Profile, isOwner: boolean = false): Promise<void> {
|
||||
const mainPhotoFileId = profile.photos[0]; // Первое фото - главное
|
||||
|
||||
let profileText = `👤 ${profile.name}, ${profile.age}\n`;
|
||||
profileText += `📍 ${profile.city || 'Не указан'}\n`;
|
||||
if (profile.job) profileText += `💼 ${profile.job}\n`;
|
||||
if (profile.education) profileText += `🎓 ${profile.education}\n`;
|
||||
if (profile.height) profileText += `📏 ${profile.height} см\n`;
|
||||
profileText += `\n📝 ${profile.bio || 'Описание не указано'}\n`;
|
||||
|
||||
if (profile.interests.length > 0) {
|
||||
profileText += `\n🎯 Интересы: ${profile.interests.join(', ')}`;
|
||||
}
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = isOwner ? {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '✏️ Редактировать', callback_data: 'edit_profile' },
|
||||
{ text: '📸 Фото', callback_data: 'manage_photos' }
|
||||
],
|
||||
[{ text: '🔍 Начать поиск', callback_data: 'start_browsing' }]
|
||||
]
|
||||
} : {
|
||||
inline_keyboard: [
|
||||
[{ text: '👈 Назад', callback_data: 'back_to_browsing' }]
|
||||
]
|
||||
};
|
||||
|
||||
if (mainPhotoFileId) {
|
||||
await this.bot.sendPhoto(chatId, mainPhotoFileId, {
|
||||
caption: profileText,
|
||||
reply_markup: keyboard
|
||||
});
|
||||
} else {
|
||||
await this.bot.sendMessage(chatId, profileText, { reply_markup: keyboard });
|
||||
}
|
||||
}
|
||||
|
||||
async showNextCandidate(chatId: number, userId: string): Promise<void> {
|
||||
const candidate = await this.matchingService.getNextCandidate(userId);
|
||||
|
||||
if (!candidate) {
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'🎉 Вы просмотрели всех доступных кандидатов!\n\n' +
|
||||
'⏰ Попробуйте позже - возможно появятся новые анкеты!'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const candidatePhotoFileId = candidate.photos[0]; // Первое фото - главное
|
||||
|
||||
let candidateText = `${candidate.name}, ${candidate.age}\n`;
|
||||
candidateText += `📍 ${candidate.city || 'Не указан'}\n`;
|
||||
if (candidate.job) candidateText += `💼 ${candidate.job}\n`;
|
||||
if (candidate.education) candidateText += `🎓 ${candidate.education}\n`;
|
||||
if (candidate.height) candidateText += `📏 ${candidate.height} см\n`;
|
||||
candidateText += `\n📝 ${candidate.bio || 'Описание отсутствует'}\n`;
|
||||
|
||||
if (candidate.interests.length > 0) {
|
||||
candidateText += `\n🎯 Интересы: ${candidate.interests.join(', ')}`;
|
||||
}
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '👎 Не нравится', callback_data: `dislike_${candidate.userId}` },
|
||||
{ text: '💖 Супер лайк', callback_data: `superlike_${candidate.userId}` },
|
||||
{ text: '👍 Нравится', callback_data: `like_${candidate.userId}` }
|
||||
],
|
||||
[
|
||||
{ text: '👤 Профиль', callback_data: `view_profile_${candidate.userId}` },
|
||||
{ text: '📸 Еще фото', callback_data: `more_photos_${candidate.userId}` }
|
||||
],
|
||||
[{ text: '⏭ Следующий', callback_data: 'next_candidate' }]
|
||||
]
|
||||
};
|
||||
|
||||
if (candidatePhotoFileId) {
|
||||
await this.bot.sendPhoto(chatId, candidatePhotoFileId, {
|
||||
caption: candidateText,
|
||||
reply_markup: keyboard
|
||||
});
|
||||
} else {
|
||||
await this.bot.sendMessage(chatId, candidateText, { reply_markup: keyboard });
|
||||
}
|
||||
}
|
||||
}
|
||||
315
src/handlers/messageHandlers.ts
Normal file
315
src/handlers/messageHandlers.ts
Normal file
@@ -0,0 +1,315 @@
|
||||
import TelegramBot, { Message, InlineKeyboardMarkup } from 'node-telegram-bot-api';
|
||||
import { ProfileService } from '../services/profileService';
|
||||
import { ChatService } from '../services/chatService';
|
||||
|
||||
// Состояния пользователей для создания профилей
|
||||
interface UserState {
|
||||
step: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
// Состояния пользователей для чатов
|
||||
interface ChatState {
|
||||
waitingForMessage: boolean;
|
||||
matchId: string;
|
||||
}
|
||||
|
||||
export class MessageHandlers {
|
||||
private bot: TelegramBot;
|
||||
private profileService: ProfileService;
|
||||
private chatService: ChatService;
|
||||
private userStates: Map<string, UserState> = new Map();
|
||||
private chatStates: Map<string, ChatState> = new Map();
|
||||
|
||||
constructor(bot: TelegramBot) {
|
||||
this.bot = bot;
|
||||
this.profileService = new ProfileService();
|
||||
this.chatService = new ChatService();
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.bot.on('message', (msg: Message) => {
|
||||
// Игнорируем команды (они обрабатываются CommandHandlers)
|
||||
if (!msg.text?.startsWith('/')) {
|
||||
this.handleMessage(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async handleMessage(msg: Message): Promise<void> {
|
||||
const userId = msg.from?.id.toString();
|
||||
if (!userId) return;
|
||||
|
||||
const userState = this.userStates.get(userId);
|
||||
const chatState = this.chatStates.get(userId);
|
||||
|
||||
// Если пользователь в процессе отправки сообщения в чат
|
||||
if (chatState?.waitingForMessage && msg.text) {
|
||||
await this.handleChatMessage(msg, userId, chatState.matchId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Если пользователь в процессе создания профиля
|
||||
if (userState) {
|
||||
await this.handleProfileCreation(msg, userId, userState);
|
||||
return;
|
||||
}
|
||||
|
||||
// Обычные сообщения
|
||||
if (msg.text) {
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'Привет! 👋\n\n' +
|
||||
'Используйте команды для навигации:\n' +
|
||||
'/start - Главное меню\n' +
|
||||
'/help - Справка\n' +
|
||||
'/profile - Мой профиль\n' +
|
||||
'/browse - Поиск анкет'
|
||||
);
|
||||
} else if (msg.photo) {
|
||||
// Обработка фотографий (для добавления в профиль)
|
||||
await this.handlePhoto(msg, userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка создания профиля
|
||||
async handleProfileCreation(msg: Message, userId: string, userState: UserState): Promise<void> {
|
||||
const chatId = msg.chat.id;
|
||||
|
||||
try {
|
||||
switch (userState.step) {
|
||||
case 'waiting_name':
|
||||
if (!msg.text) {
|
||||
await this.bot.sendMessage(chatId, '❌ Пожалуйста, отправьте текстовое сообщение с вашим именем');
|
||||
return;
|
||||
}
|
||||
|
||||
userState.data.name = msg.text.trim();
|
||||
userState.step = 'waiting_age';
|
||||
|
||||
await this.bot.sendMessage(chatId, '📅 Отлично! Теперь укажите ваш возраст:');
|
||||
break;
|
||||
|
||||
case 'waiting_age':
|
||||
if (!msg.text) {
|
||||
await this.bot.sendMessage(chatId, '❌ Пожалуйста, отправьте число');
|
||||
return;
|
||||
}
|
||||
|
||||
const age = parseInt(msg.text.trim());
|
||||
if (isNaN(age) || age < 18 || age > 100) {
|
||||
await this.bot.sendMessage(chatId, '❌ Возраст должен быть числом от 18 до 100');
|
||||
return;
|
||||
}
|
||||
|
||||
userState.data.age = age;
|
||||
userState.step = 'waiting_city';
|
||||
|
||||
await this.bot.sendMessage(chatId, '📍 Прекрасно! В каком городе вы живете?');
|
||||
break;
|
||||
|
||||
case 'waiting_city':
|
||||
if (!msg.text) {
|
||||
await this.bot.sendMessage(chatId, '❌ Пожалуйста, отправьте название города');
|
||||
return;
|
||||
}
|
||||
|
||||
userState.data.city = msg.text.trim();
|
||||
userState.step = 'waiting_bio';
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'📝 Теперь расскажите немного о себе (био):\n\n' +
|
||||
'💡 Например: хобби, интересы, что ищете в отношениях и т.д.'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'waiting_bio':
|
||||
if (!msg.text) {
|
||||
await this.bot.sendMessage(chatId, '❌ Пожалуйста, отправьте текстовое описание');
|
||||
return;
|
||||
}
|
||||
|
||||
userState.data.bio = msg.text.trim();
|
||||
userState.step = 'waiting_photo';
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'📸 Отлично! Теперь отправьте ваше фото:\n\n' +
|
||||
'💡 Лучше использовать качественное фото лица'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'waiting_photo':
|
||||
if (!msg.photo) {
|
||||
await this.bot.sendMessage(chatId, '❌ Пожалуйста, отправьте фотографию');
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем самое большое фото
|
||||
const photo = msg.photo[msg.photo.length - 1];
|
||||
userState.data.photos = [photo.file_id]; // Просто массив file_id
|
||||
|
||||
// Создаем профиль
|
||||
await this.createProfile(chatId, userId, userState.data);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.userStates.delete(userId);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Profile creation error:', error);
|
||||
await this.bot.sendMessage(chatId, '❌ Произошла ошибка. Попробуйте еще раз.');
|
||||
this.userStates.delete(userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Создание профиля в базе данных
|
||||
async createProfile(chatId: number, telegramId: string, profileData: any): Promise<void> {
|
||||
try {
|
||||
// Сначала создаем пользователя если не существует
|
||||
const userId = await this.profileService.ensureUser(telegramId, {
|
||||
username: '', // Можно получить из Telegram API если нужно
|
||||
first_name: profileData.name,
|
||||
last_name: ''
|
||||
});
|
||||
|
||||
// Определяем интересы по умолчанию
|
||||
const interestedIn = profileData.gender === 'male' ? 'female' :
|
||||
profileData.gender === 'female' ? 'male' : 'both';
|
||||
|
||||
const newProfile = await this.profileService.createProfile(userId, {
|
||||
name: profileData.name,
|
||||
age: profileData.age,
|
||||
gender: profileData.gender,
|
||||
interestedIn: interestedIn,
|
||||
bio: profileData.bio,
|
||||
city: profileData.city,
|
||||
photos: profileData.photos,
|
||||
interests: [],
|
||||
searchPreferences: {
|
||||
minAge: Math.max(18, profileData.age - 10),
|
||||
maxAge: Math.min(100, profileData.age + 10),
|
||||
maxDistance: 50
|
||||
}
|
||||
});
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '👤 Мой профиль', callback_data: 'view_my_profile' },
|
||||
{ text: '🔍 Начать поиск', callback_data: 'start_browsing' }
|
||||
],
|
||||
[{ text: '⚙️ Настройки', callback_data: 'settings' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
`🎉 Профиль успешно создан!\n\n` +
|
||||
`Добро пожаловать, ${profileData.name}! 💖\n\n` +
|
||||
`Теперь вы можете начать поиск своей второй половинки!`,
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
|
||||
// Удаляем состояние пользователя
|
||||
this.userStates.delete(telegramId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating profile:', error);
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'❌ Ошибка при создании профиля. Попробуйте еще раз позже.'
|
||||
);
|
||||
this.userStates.delete(telegramId);
|
||||
}
|
||||
}
|
||||
|
||||
// Обработка фотографий
|
||||
async handlePhoto(msg: Message, userId: string): Promise<void> {
|
||||
const userState = this.userStates.get(userId);
|
||||
|
||||
if (userState && userState.step === 'waiting_photo') {
|
||||
// Фото для создания профиля - обрабатывается выше
|
||||
return;
|
||||
}
|
||||
|
||||
// Фото для существующего профиля
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'📸 Для управления фотографиями используйте:\n' +
|
||||
'/profile - затем "📸 Фото"'
|
||||
);
|
||||
}
|
||||
|
||||
// Методы для инициализации создания профиля
|
||||
startProfileCreation(userId: string, gender: string): void {
|
||||
this.userStates.set(userId, {
|
||||
step: 'waiting_name',
|
||||
data: { gender }
|
||||
});
|
||||
}
|
||||
|
||||
// Получить состояние пользователя
|
||||
getUserState(userId: string): UserState | undefined {
|
||||
return this.userStates.get(userId);
|
||||
}
|
||||
|
||||
// Очистить состояние пользователя
|
||||
clearUserState(userId: string): void {
|
||||
this.userStates.delete(userId);
|
||||
}
|
||||
|
||||
// Методы для управления чатами
|
||||
setWaitingForMessage(userId: string, matchId: string): void {
|
||||
this.chatStates.set(userId, {
|
||||
waitingForMessage: true,
|
||||
matchId
|
||||
});
|
||||
}
|
||||
|
||||
clearChatState(userId: string): void {
|
||||
this.chatStates.delete(userId);
|
||||
}
|
||||
|
||||
// Обработка сообщения в чате
|
||||
async handleChatMessage(msg: Message, userId: string, matchId: string): Promise<void> {
|
||||
if (!msg.text) {
|
||||
await this.bot.sendMessage(msg.chat.id, '❌ Поддерживаются только текстовые сообщения');
|
||||
return;
|
||||
}
|
||||
|
||||
// Отправляем сообщение
|
||||
const message = await this.chatService.sendMessage(matchId, userId, msg.text);
|
||||
|
||||
if (message) {
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'✅ Сообщение отправлено!\n\n' +
|
||||
`💬 "${msg.text}"`
|
||||
);
|
||||
|
||||
// Очищаем состояние чата
|
||||
this.clearChatState(userId);
|
||||
|
||||
// Возвращаемся к чату
|
||||
setTimeout(async () => {
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
[{ text: '← Вернуться к чату', callback_data: `chat_${matchId}` }],
|
||||
[{ text: '💬 Все чаты', callback_data: 'open_chats' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'💬 Что дальше?',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}, 1500);
|
||||
} else {
|
||||
await this.bot.sendMessage(msg.chat.id, '❌ Не удалось отправить сообщение. Попробуйте еще раз.');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user