390 lines
17 KiB
TypeScript
390 lines
17 KiB
TypeScript
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.profileService = new ProfileService();
|
||
this.notificationService = new NotificationService(bot);
|
||
this.chatService = new ChatService(this.notificationService);
|
||
}
|
||
|
||
// ===== НАТИВНЫЙ ИНТЕРФЕЙС ЧАТОВ =====
|
||
|
||
// Показать список чатов в более простом формате
|
||
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, '❌ Ошибка при открытии чата');
|
||
}
|
||
}
|
||
|
||
// ===== СИСТЕМА УВЕДОМЛЕНИЙ =====
|
||
|
||
// Отправить уведомление о новом сообщении - теперь используем NotificationService
|
||
async sendMessageNotification(receiverTelegramId: string, senderName: string, messagePreview: string, matchId: string): Promise<void> {
|
||
try {
|
||
// Получаем идентификаторы пользователей для использования в NotificationService
|
||
const receiverUserId = await this.profileService.getUserIdByTelegramId(receiverTelegramId);
|
||
const sender = await this.chatService.getMatchInfo(matchId, receiverTelegramId);
|
||
|
||
if (!receiverUserId || !sender?.otherUserId) {
|
||
console.error('Failed to get user IDs for notification');
|
||
return;
|
||
}
|
||
|
||
// Используем сервис уведомлений для отправки более красивого уведомления
|
||
await this.notificationService.sendMessageNotification(
|
||
receiverUserId,
|
||
sender.otherUserId,
|
||
messagePreview,
|
||
matchId
|
||
);
|
||
} 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.photo ?
|
||
(msg.caption || '[Фото]') + ' [file_id: ' + msg.photo[msg.photo.length - 1].file_id + ']' :
|
||
(msg.text || '[Медиа]'),
|
||
msg.photo ? 'photo' : 'text'
|
||
);
|
||
|
||
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, '❌ Ошибка при загрузке истории');
|
||
}
|
||
}
|
||
}
|