init commit

This commit is contained in:
2025-09-12 21:25:54 +09:00
commit 17efb2fb53
37 changed files with 12637 additions and 0 deletions

View 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
});
}
}
}

View 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 });
}
}
}

View 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, '❌ Не удалось отправить сообщение. Попробуйте еще раз.');
}
}
}