Fix like/dislike errors and implement native chat system
This commit is contained in:
@@ -5,6 +5,7 @@ import { ChatService } from '../services/chatService';
|
||||
import { Profile } from '../models/Profile';
|
||||
import { MessageHandlers } from './messageHandlers';
|
||||
import { ProfileEditController } from '../controllers/profileEditController';
|
||||
import { EnhancedChatHandlers } from './enhancedChatHandlers';
|
||||
|
||||
export class CallbackHandlers {
|
||||
private bot: TelegramBot;
|
||||
@@ -13,6 +14,7 @@ export class CallbackHandlers {
|
||||
private chatService: ChatService;
|
||||
private messageHandlers: MessageHandlers;
|
||||
private profileEditController: ProfileEditController;
|
||||
private enhancedChatHandlers: EnhancedChatHandlers;
|
||||
|
||||
constructor(bot: TelegramBot, messageHandlers: MessageHandlers) {
|
||||
this.bot = bot;
|
||||
@@ -21,6 +23,7 @@ export class CallbackHandlers {
|
||||
this.chatService = new ChatService();
|
||||
this.messageHandlers = messageHandlers;
|
||||
this.profileEditController = new ProfileEditController(this.profileService);
|
||||
this.enhancedChatHandlers = new EnhancedChatHandlers(bot);
|
||||
}
|
||||
|
||||
register(): void {
|
||||
@@ -156,6 +159,14 @@ export class CallbackHandlers {
|
||||
await this.handleViewMatches(chatId, telegramId);
|
||||
} else if (data === 'open_chats') {
|
||||
await this.handleOpenChats(chatId, telegramId);
|
||||
} else if (data === 'native_chats') {
|
||||
await this.enhancedChatHandlers.showChatsNative(chatId, telegramId);
|
||||
} else if (data.startsWith('open_native_chat_')) {
|
||||
const matchId = data.replace('open_native_chat_', '');
|
||||
await this.enhancedChatHandlers.openNativeChat(chatId, telegramId, matchId);
|
||||
} else if (data.startsWith('chat_history_')) {
|
||||
const matchId = data.replace('chat_history_', '');
|
||||
await this.enhancedChatHandlers.showChatHistory(chatId, telegramId, matchId);
|
||||
} else if (data.startsWith('chat_')) {
|
||||
const matchId = data.replace('chat_', '');
|
||||
await this.handleOpenChat(chatId, telegramId, matchId);
|
||||
@@ -180,6 +191,18 @@ export class CallbackHandlers {
|
||||
await this.handleSearchSettings(chatId, telegramId);
|
||||
} else if (data === 'notification_settings') {
|
||||
await this.handleNotificationSettings(chatId, telegramId);
|
||||
} else if (data === 'view_stats') {
|
||||
await this.handleViewStats(chatId, telegramId);
|
||||
} else if (data === 'view_profile_viewers') {
|
||||
await this.handleViewProfileViewers(chatId, telegramId);
|
||||
} else if (data === 'hide_profile') {
|
||||
await this.handleHideProfile(chatId, telegramId);
|
||||
} else if (data === 'delete_profile') {
|
||||
await this.handleDeleteProfile(chatId, telegramId);
|
||||
} else if (data === 'main_menu') {
|
||||
await this.handleMainMenu(chatId, telegramId);
|
||||
} else if (data === 'confirm_delete_profile') {
|
||||
await this.handleConfirmDeleteProfile(chatId, telegramId);
|
||||
}
|
||||
|
||||
// Информация
|
||||
@@ -187,6 +210,8 @@ export class CallbackHandlers {
|
||||
await this.handleHowItWorks(chatId);
|
||||
} else if (data === 'back_to_browsing') {
|
||||
await this.handleStartBrowsing(chatId, telegramId);
|
||||
} else if (data === 'get_vip') {
|
||||
await this.handleGetVip(chatId, telegramId);
|
||||
}
|
||||
|
||||
else {
|
||||
@@ -278,7 +303,13 @@ export class CallbackHandlers {
|
||||
// Лайк
|
||||
async handleLike(chatId: number, telegramId: string, targetUserId: string): Promise<void> {
|
||||
try {
|
||||
const result = await this.matchingService.performSwipe(telegramId, targetUserId, 'like');
|
||||
// Получаем telegram_id целевого пользователя
|
||||
const targetTelegramId = await this.profileService.getTelegramIdByUserId(targetUserId);
|
||||
if (!targetTelegramId) {
|
||||
throw new Error('Target user not found');
|
||||
}
|
||||
|
||||
const result = await this.matchingService.performSwipe(telegramId, targetTelegramId, 'like');
|
||||
|
||||
if (result.isMatch) {
|
||||
// Это матч!
|
||||
@@ -314,7 +345,13 @@ export class CallbackHandlers {
|
||||
// Дизлайк
|
||||
async handleDislike(chatId: number, telegramId: string, targetUserId: string): Promise<void> {
|
||||
try {
|
||||
await this.matchingService.performSwipe(telegramId, targetUserId, 'pass');
|
||||
// Получаем telegram_id целевого пользователя
|
||||
const targetTelegramId = await this.profileService.getTelegramIdByUserId(targetUserId);
|
||||
if (!targetTelegramId) {
|
||||
throw new Error('Target user not found');
|
||||
}
|
||||
|
||||
await this.matchingService.performSwipe(telegramId, targetTelegramId, 'pass');
|
||||
await this.showNextCandidate(chatId, telegramId);
|
||||
} catch (error) {
|
||||
await this.bot.sendMessage(chatId, '❌ Ошибка при отправке дизлайка');
|
||||
@@ -325,7 +362,13 @@ export class CallbackHandlers {
|
||||
// Супер лайк
|
||||
async handleSuperlike(chatId: number, telegramId: string, targetUserId: string): Promise<void> {
|
||||
try {
|
||||
const result = await this.matchingService.performSwipe(telegramId, targetUserId, 'superlike');
|
||||
// Получаем telegram_id целевого пользователя
|
||||
const targetTelegramId = await this.profileService.getTelegramIdByUserId(targetUserId);
|
||||
if (!targetTelegramId) {
|
||||
throw new Error('Target user not found');
|
||||
}
|
||||
|
||||
const result = await this.matchingService.performSwipe(telegramId, targetTelegramId, 'superlike');
|
||||
|
||||
if (result.isMatch) {
|
||||
const targetProfile = await this.profileService.getProfileByUserId(targetUserId);
|
||||
@@ -366,6 +409,12 @@ export class CallbackHandlers {
|
||||
return;
|
||||
}
|
||||
|
||||
// Записываем просмотр профиля
|
||||
const targetTelegramId = await this.profileService.getTelegramIdByUserId(targetProfile.userId);
|
||||
if (targetTelegramId) {
|
||||
await this.profileService.recordProfileView(telegramId, targetTelegramId, 'profile_view');
|
||||
}
|
||||
|
||||
await this.showProfile(chatId, targetProfile, false, telegramId);
|
||||
}
|
||||
|
||||
@@ -643,7 +692,11 @@ export class CallbackHandlers {
|
||||
{ text: '🔔 Уведомления', callback_data: 'notification_settings' }
|
||||
],
|
||||
[
|
||||
{ text: '🚫 Скрыть профиль', callback_data: 'hide_profile' },
|
||||
{ text: '<EFBFBD> Статистика', callback_data: 'view_stats' },
|
||||
{ text: '👀 Кто смотрел', callback_data: 'view_profile_viewers' }
|
||||
],
|
||||
[
|
||||
{ text: '<27>🚫 Скрыть профиль', callback_data: 'hide_profile' },
|
||||
{ text: '🗑 Удалить профиль', callback_data: 'delete_profile' }
|
||||
]
|
||||
]
|
||||
@@ -812,6 +865,12 @@ export class CallbackHandlers {
|
||||
return;
|
||||
}
|
||||
|
||||
// Записываем просмотр кандидата
|
||||
const candidateTelegramId = await this.profileService.getTelegramIdByUserId(candidate.userId);
|
||||
if (candidateTelegramId) {
|
||||
await this.profileService.recordProfileView(telegramId, candidateTelegramId, 'browse');
|
||||
}
|
||||
|
||||
const candidatePhotoFileId = candidate.photos[0]; // Первое фото - главное
|
||||
|
||||
let candidateText = candidate.name + ', ' + candidate.age + '\n';
|
||||
@@ -1577,4 +1636,254 @@ export class CallbackHandlers {
|
||||
|
||||
return parts.join(', ');
|
||||
}
|
||||
|
||||
// Просмотр статистики профиля
|
||||
async handleViewStats(chatId: number, telegramId: string): Promise<void> {
|
||||
try {
|
||||
const profile = await this.profileService.getProfileByTelegramId(telegramId);
|
||||
if (!profile) {
|
||||
await this.bot.sendMessage(chatId, '❌ Профиль не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = await this.profileService.getProfileStats(profile.userId);
|
||||
|
||||
const statsText = `📊 Статистика вашего профиля\n\n` +
|
||||
`💖 Ваши лайки: ${stats.totalLikes}\n` +
|
||||
`💕 Матчи: ${stats.totalMatches}\n` +
|
||||
`👀 Просмотры профиля: ${stats.profileViews}\n` +
|
||||
`❤️ Лайки получено: ${stats.likesReceived}\n\n` +
|
||||
`💡 Совет: Заполните все поля профиля и добавьте качественные фото для большего успеха!`;
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '👀 Кто смотрел профиль', callback_data: 'view_profile_viewers' }],
|
||||
[{ text: '🔙 Назад', callback_data: 'settings' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(chatId, statsText, { reply_markup: keyboard });
|
||||
} catch (error) {
|
||||
console.error('Error viewing stats:', error);
|
||||
await this.bot.sendMessage(chatId, '❌ Произошла ошибка при получении статистики');
|
||||
}
|
||||
}
|
||||
|
||||
// Просмотр кто смотрел профиль
|
||||
async handleViewProfileViewers(chatId: number, telegramId: string): Promise<void> {
|
||||
try {
|
||||
const profile = await this.profileService.getProfileByTelegramId(telegramId);
|
||||
if (!profile) {
|
||||
await this.bot.sendMessage(chatId, '❌ Профиль не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
const viewers = await this.profileService.getProfileViewers(profile.userId, 10);
|
||||
|
||||
if (viewers.length === 0) {
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'👁️ Пока никто не просматривал ваш профиль\n\n' +
|
||||
'Начните искать анкеты, чтобы другие пользователи тоже могли вас найти!',
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[{ text: '🔍 Начать поиск', callback_data: 'start_browsing' }],
|
||||
[{ text: '🔙 Назад', callback_data: 'settings' }]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let viewersText = '👀 Последние просмотры вашего профиля:\n\n';
|
||||
viewers.forEach((viewer, index) => {
|
||||
viewersText += `${index + 1}. ${viewer.name}, ${viewer.age}\n`;
|
||||
if (viewer.city) viewersText += ` 📍 ${viewer.city}\n`;
|
||||
viewersText += '\n';
|
||||
});
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '📊 Статистика', callback_data: 'view_stats' }],
|
||||
[{ text: '🔙 Назад', callback_data: 'settings' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(chatId, viewersText, { reply_markup: keyboard });
|
||||
} catch (error) {
|
||||
console.error('Error viewing profile viewers:', error);
|
||||
await this.bot.sendMessage(chatId, '❌ Произошла ошибка при получении информации о просмотрах');
|
||||
}
|
||||
}
|
||||
|
||||
// Возврат в главное меню
|
||||
async handleMainMenu(chatId: number, telegramId: string): Promise<void> {
|
||||
// Используем существующий метод handleStart из CommandHandlers
|
||||
const profile = await this.profileService.getProfileByTelegramId(telegramId);
|
||||
|
||||
if (profile) {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '👤 Мой профиль', callback_data: 'view_my_profile' },
|
||||
{ text: '🔍 Просмотр анкет', callback_data: 'start_browsing' }
|
||||
],
|
||||
[
|
||||
{ text: '💕 Мои матчи', callback_data: 'view_matches' },
|
||||
{ text: '⭐ VIP поиск', callback_data: 'vip_search' }
|
||||
],
|
||||
[
|
||||
{ text: '⚙️ Настройки', callback_data: 'settings' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
`🎉 Добро пожаловать, ${profile.name}!\n\n` +
|
||||
`💖 Telegram Tinder Bot готов к работе!\n\n` +
|
||||
`Что хотите сделать?`,
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
} else {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '✨ Создать профиль', callback_data: 'create_profile' }],
|
||||
[{ text: 'ℹ️ Как это работает?', callback_data: 'how_it_works' }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
`🎉 Добро пожаловать в Telegram Tinder Bot!\n\n` +
|
||||
`💕 Здесь вы можете найти свою вторую половинку!\n\n` +
|
||||
`Для начала создайте свой профиль:`,
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Скрыть/показать профиль
|
||||
async handleHideProfile(chatId: number, telegramId: string): Promise<void> {
|
||||
try {
|
||||
const profile = await this.profileService.getProfileByTelegramId(telegramId);
|
||||
if (!profile) {
|
||||
await this.bot.sendMessage(chatId, '❌ Профиль не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
const newVisibility = !profile.isVisible;
|
||||
await this.profileService.toggleVisibility(profile.userId);
|
||||
|
||||
const statusText = newVisibility ? 'видимым' : 'скрытым';
|
||||
const emoji = newVisibility ? '👁️' : '🙈';
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
`${emoji} Ваш профиль теперь ${statusText}!\n\n` +
|
||||
(newVisibility
|
||||
? '✅ Другие пользователи смогут найти вас в поиске'
|
||||
: '🔒 Ваш профиль скрыт и не отображается в поиске'),
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[{ text: '🔙 Назад к настройкам', callback_data: 'settings' }]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error toggling profile visibility:', error);
|
||||
await this.bot.sendMessage(chatId, '❌ Произошла ошибка при изменении видимости профиля');
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление профиля
|
||||
async handleDeleteProfile(chatId: number, telegramId: string): Promise<void> {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '✅ Да, удалить', callback_data: 'confirm_delete_profile' },
|
||||
{ text: '❌ Отмена', callback_data: 'settings' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'⚠️ ВНИМАНИЕ!\n\n' +
|
||||
'🗑️ Вы действительно хотите удалить свой профиль?\n\n' +
|
||||
'❗ Это действие нельзя отменить:\n' +
|
||||
'• Все ваши фото будут удалены\n' +
|
||||
'• Все матчи и переписки исчезнут\n' +
|
||||
'• Придется создавать профиль заново\n\n' +
|
||||
'🤔 Подумайте еще раз!',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
}
|
||||
|
||||
// Подтверждение удаления профиля
|
||||
async handleConfirmDeleteProfile(chatId: number, telegramId: string): Promise<void> {
|
||||
try {
|
||||
const profile = await this.profileService.getProfileByTelegramId(telegramId);
|
||||
if (!profile) {
|
||||
await this.bot.sendMessage(chatId, '❌ Профиль не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
// Удаляем профиль (это также удалит связанные данные через CASCADE)
|
||||
await this.profileService.deleteProfile(profile.userId);
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'💔 Ваш профиль успешно удален\n\n' +
|
||||
'😢 Мы будем скучать! Но вы всегда можете вернуться и создать новый профиль.\n\n' +
|
||||
'👋 До свидания!',
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[{ text: '✨ Создать новый профиль', callback_data: 'create_profile' }]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error deleting profile:', error);
|
||||
await this.bot.sendMessage(chatId, '❌ Произошла ошибка при удалении профиля');
|
||||
}
|
||||
}
|
||||
|
||||
// Получение VIP статуса
|
||||
async handleGetVip(chatId: number, telegramId: string): Promise<void> {
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'💎 *VIP Статус*\n\n' +
|
||||
'✨ VIP дает вам дополнительные возможности:\n\n' +
|
||||
'🔍 *Расширенный поиск* - найти людей по интересам\n' +
|
||||
'📊 *Подробная статистика* - кто смотрел ваш профиль\n' +
|
||||
'💕 *Больше лайков* - отправляйте до 100 лайков в день\n' +
|
||||
'⭐ *Приоритет в поиске* - ваш профиль показывается первым\n' +
|
||||
'🎯 *Суперлайки* - выделите себя среди других\n\n' +
|
||||
'💰 *Стоимость:*\n' +
|
||||
'• 1 месяц - 299₽\n' +
|
||||
'• 3 месяца - 699₽ (экономия 200₽)\n' +
|
||||
'• 6 месяцев - 1199₽ (экономия 600₽)\n\n' +
|
||||
'📞 Для получения VIP свяжитесь с администратором: @admin',
|
||||
{
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '📞 Связаться с админом', url: 'https://t.me/admin' }
|
||||
],
|
||||
[
|
||||
{ text: '🔙 Назад', callback_data: 'vip_search' }
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,8 @@ export class CommandHandlers {
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '💬 Открыть чаты', callback_data: 'open_chats' }],
|
||||
[{ text: '🔍 Найти еще', callback_data: 'start_browsing' }]
|
||||
[{ text: '<EFBFBD> Нативные чаты', callback_data: 'native_chats' }],
|
||||
[{ text: '<27>🔍 Найти еще', callback_data: 'start_browsing' }]
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
389
src/handlers/enhancedChatHandlers.ts
Normal file
389
src/handlers/enhancedChatHandlers.ts
Normal file
@@ -0,0 +1,389 @@
|
||||
import TelegramBot, { InlineKeyboardMarkup } from 'node-telegram-bot-api';
|
||||
import { ChatService } from '../services/chatService';
|
||||
import { ProfileService } from '../services/profileService';
|
||||
import { NotificationService } from '../services/notificationService';
|
||||
|
||||
export class EnhancedChatHandlers {
|
||||
private bot: TelegramBot;
|
||||
private chatService: ChatService;
|
||||
private profileService: ProfileService;
|
||||
private notificationService: NotificationService;
|
||||
|
||||
constructor(bot: TelegramBot) {
|
||||
this.bot = bot;
|
||||
this.chatService = new ChatService();
|
||||
this.profileService = new ProfileService();
|
||||
this.notificationService = new NotificationService(bot);
|
||||
}
|
||||
|
||||
// ===== НАТИВНЫЙ ИНТЕРФЕЙС ЧАТОВ =====
|
||||
|
||||
// Показать список чатов в более простом формате
|
||||
async showChatsNative(chatId: number, telegramId: string): Promise<void> {
|
||||
try {
|
||||
const chats = await this.chatService.getUserChats(telegramId);
|
||||
|
||||
if (chats.length === 0) {
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
'💬 *Чаты*\n\n' +
|
||||
'😔 У вас пока нет активных чатов\n\n' +
|
||||
'💡 Ставьте лайки, чтобы найти матчи и начать общение!',
|
||||
{
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[{ text: '🔍 Найти людей', callback_data: 'start_browsing' }],
|
||||
[{ text: '🏠 Главное меню', callback_data: 'main_menu' }]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Создаем простые кнопки для каждого чата
|
||||
const chatButtons: any[][] = [];
|
||||
|
||||
for (const chat of chats.slice(0, 10)) { // Показываем максимум 10 чатов
|
||||
const unreadBadge = chat.unreadCount > 0 ? ` 🔴${chat.unreadCount}` : '';
|
||||
const lastMessagePreview = this.getMessagePreview(chat.lastMessage);
|
||||
|
||||
chatButtons.push([{
|
||||
text: `💬 ${chat.otherUserName}${unreadBadge}`,
|
||||
callback_data: `open_native_chat_${chat.matchId}`
|
||||
}]);
|
||||
}
|
||||
|
||||
// Добавляем кнопки управления
|
||||
chatButtons.push([
|
||||
{ text: '🔍 Найти людей', callback_data: 'start_browsing' },
|
||||
{ text: '🏠 Главное меню', callback_data: 'main_menu' }
|
||||
]);
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
`💬 *Ваши чаты* (${chats.length})\n\n` +
|
||||
'👆 Выберите чат для общения:',
|
||||
{
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: { inline_keyboard: chatButtons }
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error showing native chats:', error);
|
||||
await this.bot.sendMessage(chatId, '❌ Ошибка при загрузке чатов');
|
||||
}
|
||||
}
|
||||
|
||||
// Открыть чат в нативном формате
|
||||
async openNativeChat(chatId: number, telegramId: string, matchId: string): Promise<void> {
|
||||
try {
|
||||
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, 20);
|
||||
|
||||
const otherUserName = matchInfo.otherUserProfile?.name || 'Пользователь';
|
||||
|
||||
if (messages.length === 0) {
|
||||
// Первое сообщение в чате
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
`💕 *Новый матч с ${otherUserName}!*\n\n` +
|
||||
'🎉 Поздравляем! Вы понравились друг другу!\n\n' +
|
||||
'💬 *Как начать общение?*\n' +
|
||||
'• Просто напишите сообщение в этот чат\n' +
|
||||
'• Ваше сообщение будет доставлено собеседнику\n' +
|
||||
'• Он получит уведомление о новом сообщении\n\n' +
|
||||
'💡 Напишите что-нибудь интересное!',
|
||||
{
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[{ text: '👤 Профиль собеседника', callback_data: `view_chat_profile_${matchId}` }],
|
||||
[{ text: '← Назад к чатам', callback_data: 'native_chats' }]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Показываем историю сообщений
|
||||
let chatHistory = `💬 *Чат с ${otherUserName}*\n\n`;
|
||||
|
||||
const currentUserId = await this.profileService.getUserIdByTelegramId(telegramId);
|
||||
|
||||
// Показываем последние 10 сообщений
|
||||
for (const message of messages.slice(-10)) {
|
||||
const isFromMe = message.senderId === currentUserId;
|
||||
const senderIcon = isFromMe ? '✅' : '💌';
|
||||
const time = message.createdAt.toLocaleString('ru-RU', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
chatHistory += `${senderIcon} *${time}*\n`;
|
||||
chatHistory += `${this.escapeMarkdown(message.content)}\n\n`;
|
||||
}
|
||||
|
||||
chatHistory += '💡 *Напишите сообщение в этот чат для ответа*';
|
||||
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
chatHistory,
|
||||
{
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '👤 Профиль', callback_data: `view_chat_profile_${matchId}` },
|
||||
{ text: '📜 История', callback_data: `chat_history_${matchId}` }
|
||||
],
|
||||
[{ text: '💔 Удалить матч', callback_data: `confirm_unmatch_${matchId}` }],
|
||||
[{ text: '← Назад к чатам', callback_data: 'native_chats' }]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Устанавливаем контекст чата для пользователя
|
||||
this.setUserChatContext(telegramId, matchId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error opening native chat:', error);
|
||||
await this.bot.sendMessage(chatId, '❌ Ошибка при открытии чата');
|
||||
}
|
||||
}
|
||||
|
||||
// ===== СИСТЕМА УВЕДОМЛЕНИЙ =====
|
||||
|
||||
// Отправить уведомление о новом сообщении
|
||||
async sendMessageNotification(receiverTelegramId: string, senderName: string, messagePreview: string, matchId: string): Promise<void> {
|
||||
try {
|
||||
const receiverChatId = parseInt(receiverTelegramId);
|
||||
|
||||
await this.bot.sendMessage(
|
||||
receiverChatId,
|
||||
`💌 *Новое сообщение от ${senderName}*\n\n` +
|
||||
`"${this.escapeMarkdown(messagePreview)}"\n\n` +
|
||||
'👆 Нажмите "Открыть чат" для ответа',
|
||||
{
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[{ text: '💬 Открыть чат', callback_data: `open_native_chat_${matchId}` }],
|
||||
[{ text: '📱 Все чаты', callback_data: 'native_chats' }]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error sending message notification:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== ОБРАБОТКА СООБЩЕНИЙ =====
|
||||
|
||||
// Обработать входящее сообщение для чата
|
||||
async handleIncomingChatMessage(msg: any, telegramId: string): Promise<boolean> {
|
||||
try {
|
||||
const currentChatContext = this.getUserChatContext(telegramId);
|
||||
|
||||
if (!currentChatContext) {
|
||||
return false; // Пользователь не в контексте чата
|
||||
}
|
||||
|
||||
const { matchId } = currentChatContext;
|
||||
|
||||
// Проверяем, что матч еще активен
|
||||
const matchInfo = await this.chatService.getMatchInfo(matchId, telegramId);
|
||||
if (!matchInfo) {
|
||||
await this.bot.sendMessage(msg.chat.id, '❌ Этот чат больше недоступен');
|
||||
this.clearUserChatContext(telegramId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Сохраняем сообщение
|
||||
const messageId = await this.chatService.sendMessage(
|
||||
matchId,
|
||||
telegramId,
|
||||
msg.text || '[Медиа]',
|
||||
msg.photo ? 'photo' : 'text',
|
||||
msg.photo ? msg.photo[msg.photo.length - 1].file_id : undefined
|
||||
);
|
||||
|
||||
if (messageId) {
|
||||
// Подтверждение отправки
|
||||
await this.bot.sendMessage(
|
||||
msg.chat.id,
|
||||
'✅ Сообщение отправлено!',
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[{ text: '💬 Продолжить чат', callback_data: `open_native_chat_${matchId}` }],
|
||||
[{ text: '📱 Все чаты', callback_data: 'native_chats' }]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Отправляем уведомление получателю
|
||||
const senderProfile = await this.profileService.getProfileByTelegramId(telegramId);
|
||||
const receiverTelegramId = await this.profileService.getTelegramIdByUserId(
|
||||
matchInfo.otherUserProfile?.userId || ''
|
||||
);
|
||||
|
||||
if (senderProfile && receiverTelegramId) {
|
||||
const messagePreview = this.getMessagePreview(msg.text || '[Медиа]');
|
||||
await this.sendMessageNotification(
|
||||
receiverTelegramId,
|
||||
senderProfile.name,
|
||||
messagePreview,
|
||||
matchId
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await this.bot.sendMessage(msg.chat.id, '❌ Ошибка при отправке сообщения');
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error handling incoming chat message:', error);
|
||||
await this.bot.sendMessage(msg.chat.id, '❌ Ошибка при обработке сообщения');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ===== КОНТЕКСТ ЧАТОВ =====
|
||||
|
||||
private userChatContexts: Map<string, { matchId: string; timestamp: number }> = new Map();
|
||||
|
||||
private setUserChatContext(telegramId: string, matchId: string): void {
|
||||
this.userChatContexts.set(telegramId, {
|
||||
matchId,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Автоматически очищаем контекст через 30 минут
|
||||
setTimeout(() => {
|
||||
const current = this.userChatContexts.get(telegramId);
|
||||
if (current && current.matchId === matchId) {
|
||||
this.userChatContexts.delete(telegramId);
|
||||
}
|
||||
}, 30 * 60 * 1000);
|
||||
}
|
||||
|
||||
private getUserChatContext(telegramId: string): { matchId: string } | null {
|
||||
const context = this.userChatContexts.get(telegramId);
|
||||
if (!context) return null;
|
||||
|
||||
// Проверяем, что контекст не старше 30 минут
|
||||
if (Date.now() - context.timestamp > 30 * 60 * 1000) {
|
||||
this.userChatContexts.delete(telegramId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return { matchId: context.matchId };
|
||||
}
|
||||
|
||||
private clearUserChatContext(telegramId: string): void {
|
||||
this.userChatContexts.delete(telegramId);
|
||||
}
|
||||
|
||||
// ===== ВСПОМОГАТЕЛЬНЫЕ МЕТОДЫ =====
|
||||
|
||||
private getMessagePreview(message: string | null): string {
|
||||
if (!message) return '[Пустое сообщение]';
|
||||
return message.length > 50 ? message.substring(0, 50) + '...' : message;
|
||||
}
|
||||
|
||||
private escapeMarkdown(text: string): string {
|
||||
return text.replace(/[_*[\]()~`>#+=|{}.!-]/g, '\\$&');
|
||||
}
|
||||
|
||||
// Показать расширенную историю чата
|
||||
async showChatHistory(chatId: number, telegramId: string, matchId: string): Promise<void> {
|
||||
try {
|
||||
const matchInfo = await this.chatService.getMatchInfo(matchId, telegramId);
|
||||
|
||||
if (!matchInfo) {
|
||||
await this.bot.sendMessage(chatId, '❌ Чат не найден');
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = await this.chatService.getChatMessages(matchId, 50);
|
||||
const otherUserName = matchInfo.otherUserProfile?.name || 'Пользователь';
|
||||
const currentUserId = await this.profileService.getUserIdByTelegramId(telegramId);
|
||||
|
||||
if (messages.length === 0) {
|
||||
await this.bot.sendMessage(
|
||||
chatId,
|
||||
`📜 *История с ${otherUserName}*\n\n` +
|
||||
'История сообщений пуста',
|
||||
{
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[{ text: '← Назад к чату', callback_data: `open_native_chat_${matchId}` }]
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Разбиваем сообщения на части, если их много
|
||||
const messagesPerPage = 20;
|
||||
const totalPages = Math.ceil(messages.length / messagesPerPage);
|
||||
const currentPage = 1; // Пока показываем только первую страницу
|
||||
|
||||
const startIndex = (currentPage - 1) * messagesPerPage;
|
||||
const endIndex = startIndex + messagesPerPage;
|
||||
const pageMessages = messages.slice(startIndex, endIndex);
|
||||
|
||||
let historyText = `📜 *История с ${otherUserName}*\n`;
|
||||
historyText += `📄 Страница ${currentPage} из ${totalPages}\n\n`;
|
||||
|
||||
for (const message of pageMessages) {
|
||||
const isFromMe = message.senderId === currentUserId;
|
||||
const senderIcon = isFromMe ? '✅' : '💌';
|
||||
const senderName = isFromMe ? 'Вы' : otherUserName;
|
||||
const time = message.createdAt.toLocaleString('ru-RU', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
historyText += `${senderIcon} **${senderName}** _(${time})_\n`;
|
||||
historyText += `${this.escapeMarkdown(message.content)}\n\n`;
|
||||
}
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: [
|
||||
[{ text: '← Назад к чату', callback_data: `open_native_chat_${matchId}` }]
|
||||
]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(chatId, historyText, {
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: keyboard
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error showing chat history:', error);
|
||||
await this.bot.sendMessage(chatId, '❌ Ошибка при загрузке истории');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import TelegramBot, { Message, InlineKeyboardMarkup } from 'node-telegram-bot-api';
|
||||
import { ProfileService } from '../services/profileService';
|
||||
import { ChatService } from '../services/chatService';
|
||||
import { EnhancedChatHandlers } from './enhancedChatHandlers';
|
||||
|
||||
// Состояния пользователей для создания профилей
|
||||
interface UserState {
|
||||
@@ -24,6 +25,7 @@ export class MessageHandlers {
|
||||
private bot: TelegramBot;
|
||||
private profileService: ProfileService;
|
||||
private chatService: ChatService;
|
||||
private enhancedChatHandlers: EnhancedChatHandlers;
|
||||
private userStates: Map<string, UserState> = new Map();
|
||||
private chatStates: Map<string, ChatState> = new Map();
|
||||
private profileEditStates: Map<string, ProfileEditState> = new Map();
|
||||
@@ -32,6 +34,7 @@ export class MessageHandlers {
|
||||
this.bot = bot;
|
||||
this.profileService = new ProfileService();
|
||||
this.chatService = new ChatService();
|
||||
this.enhancedChatHandlers = new EnhancedChatHandlers(bot);
|
||||
}
|
||||
|
||||
register(): void {
|
||||
@@ -51,7 +54,12 @@ export class MessageHandlers {
|
||||
const chatState = this.chatStates.get(userId);
|
||||
const profileEditState = this.profileEditStates.get(userId);
|
||||
|
||||
// Если пользователь в процессе отправки сообщения в чат
|
||||
// Проверяем на нативные чаты (прямые сообщения в контексте чата)
|
||||
if (msg.text && await this.enhancedChatHandlers.handleIncomingChatMessage(msg.chat.id, msg.text)) {
|
||||
return; // Сообщение обработано как сообщение в чате
|
||||
}
|
||||
|
||||
// Если пользователь в процессе отправки сообщения в чат (старый способ)
|
||||
if (chatState?.waitingForMessage && msg.text) {
|
||||
await this.handleChatMessage(msg, userId, chatState.matchId);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user