Fix JSON format issues with photos and add multi-photo gallery support
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import TelegramBot from 'node-telegram-bot-api';
|
||||
import { query } from '../database/connection';
|
||||
import { ProfileService } from './profileService';
|
||||
import config from '../../config/default.json';
|
||||
|
||||
export interface NotificationData {
|
||||
userId: string;
|
||||
@@ -19,6 +18,126 @@ export class NotificationService {
|
||||
this.profileService = new ProfileService();
|
||||
}
|
||||
|
||||
// Получить шаблон уведомления из базы данных или использовать встроенный
|
||||
private async getNotificationTemplate(type: string): Promise<{
|
||||
title: string;
|
||||
messageTemplate: string;
|
||||
buttonTemplate: any;
|
||||
}> {
|
||||
try {
|
||||
// Попытка получить шаблон из базы данных
|
||||
const result = await query(`
|
||||
SELECT title, message_template, button_template
|
||||
FROM notification_templates
|
||||
WHERE type = $1
|
||||
`, [type]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
return {
|
||||
title: result.rows[0].title,
|
||||
messageTemplate: result.rows[0].message_template,
|
||||
buttonTemplate: result.rows[0].button_template
|
||||
};
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log('Using default template as database is not available:', error.message);
|
||||
}
|
||||
|
||||
// Если не удалось получить из базы или произошла ошибка, используем встроенные шаблоны
|
||||
const defaultTemplates: Record<string, any> = {
|
||||
'new_like': {
|
||||
title: 'Новый лайк!',
|
||||
messageTemplate: '❤️ *{{name}}* поставил(а) вам лайк!\n\nВозраст: {{age}}\n{{city}}\n\nОтветьте взаимностью или посмотрите профиль.',
|
||||
buttonTemplate: {
|
||||
inline_keyboard: [
|
||||
[{ text: '👀 Посмотреть профиль', callback_data: 'view_profile:{{userId}}' }],
|
||||
[
|
||||
{ text: '❤️ Лайк в ответ', callback_data: 'like_back:{{userId}}' },
|
||||
{ text: '⛔️ Пропустить', callback_data: 'dislike_profile:{{userId}}' }
|
||||
],
|
||||
[{ text: '💕 Открыть все лайки', callback_data: 'view_likes' }]
|
||||
]
|
||||
}
|
||||
},
|
||||
'super_like': {
|
||||
title: 'Супер-лайк!',
|
||||
messageTemplate: '⭐️ *{{name}}* отправил(а) вам супер-лайк!\n\nВозраст: {{age}}\n{{city}}\n\nВы произвели особое впечатление! Ответьте взаимностью или посмотрите профиль.',
|
||||
buttonTemplate: {
|
||||
inline_keyboard: [
|
||||
[{ text: '👀 Посмотреть профиль', callback_data: 'view_profile:{{userId}}' }],
|
||||
[
|
||||
{ text: '❤️ Лайк в ответ', callback_data: 'like_back:{{userId}}' },
|
||||
{ text: '⛔️ Пропустить', callback_data: 'dislike_profile:{{userId}}' }
|
||||
],
|
||||
[{ text: '💕 Открыть все лайки', callback_data: 'view_likes' }]
|
||||
]
|
||||
}
|
||||
},
|
||||
'new_match': {
|
||||
title: 'Новый матч!',
|
||||
messageTemplate: '🎊 *Ура! Это взаимно!* 🎊\n\nВы и *{{name}}* понравились друг другу!\nВозраст: {{age}}\n{{city}}\n\nСделайте первый шаг - напишите сообщение!',
|
||||
buttonTemplate: {
|
||||
inline_keyboard: [
|
||||
[{ text: '💬 Начать общение', callback_data: 'open_native_chat_{{matchId}}' }],
|
||||
[
|
||||
{ text: '👀 Посмотреть профиль', callback_data: 'view_profile:{{userId}}' },
|
||||
{ text: '📋 Все матчи', callback_data: 'native_chats' }
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
'new_message': {
|
||||
title: 'Новое сообщение!',
|
||||
messageTemplate: '💌 *Новое сообщение!*\n\nОт: *{{name}}*\n\n"{{message}}"\n\nОтветьте на сообщение прямо сейчас!',
|
||||
buttonTemplate: {
|
||||
inline_keyboard: [
|
||||
[{ text: '📩 Ответить', callback_data: 'open_native_chat_{{matchId}}' }],
|
||||
[
|
||||
{ text: '👤 Профиль', callback_data: 'view_profile:{{userId}}' },
|
||||
{ text: '📋 Все чаты', callback_data: 'native_chats' }
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return defaultTemplates[type] || {
|
||||
title: 'Уведомление',
|
||||
messageTemplate: 'Новое уведомление',
|
||||
buttonTemplate: { inline_keyboard: [] }
|
||||
};
|
||||
}
|
||||
|
||||
// Применить данные к шаблону
|
||||
private applyTemplateData(template: string, data: Record<string, any>): string {
|
||||
return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
||||
return data[key] !== undefined ? data[key] : '';
|
||||
});
|
||||
}
|
||||
|
||||
// Применить данные к шаблону кнопок
|
||||
private applyTemplateDataToButtons(buttonTemplate: any, data: Record<string, any>): any {
|
||||
const result = JSON.parse(JSON.stringify(buttonTemplate)); // глубокая копия
|
||||
|
||||
// Рекурсивная функция для замены в любой вложенности
|
||||
const replaceInObject = (obj: any): any => {
|
||||
if (typeof obj === 'string') {
|
||||
return this.applyTemplateData(obj, data);
|
||||
} else if (Array.isArray(obj)) {
|
||||
return obj.map(item => replaceInObject(item));
|
||||
} else if (obj !== null && typeof obj === 'object') {
|
||||
const newObj: Record<string, any> = {};
|
||||
for (const key in obj) {
|
||||
newObj[key] = replaceInObject(obj[key]);
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
return replaceInObject(result);
|
||||
}
|
||||
|
||||
// Отправить уведомление о новом лайке
|
||||
async sendLikeNotification(targetTelegramId: string, likerTelegramId: string, isSuperLike: boolean = false): Promise<void> {
|
||||
try {
|
||||
@@ -30,25 +149,41 @@ export class NotificationService {
|
||||
if (!targetUser || !likerProfile || !this.bot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = isSuperLike
|
||||
? `⭐ ${likerProfile.name} отправил вам суперлайк!`
|
||||
: `💖 ${likerProfile.name} поставил вам лайк!`;
|
||||
|
||||
|
||||
// Получаем шаблон уведомления
|
||||
const templateType = isSuperLike ? 'super_like' : 'new_like';
|
||||
const template = await this.getNotificationTemplate(templateType);
|
||||
|
||||
// Подготовка данных для шаблона
|
||||
const templateData = {
|
||||
name: likerProfile.name,
|
||||
age: likerProfile.age.toString(),
|
||||
city: likerProfile.city || '',
|
||||
userId: likerProfile.userId
|
||||
};
|
||||
|
||||
// Применяем данные к шаблону сообщения
|
||||
const message = this.applyTemplateData(template.messageTemplate, templateData);
|
||||
|
||||
// Применяем данные к шаблону кнопок
|
||||
const keyboard = this.applyTemplateDataToButtons(template.buttonTemplate, templateData);
|
||||
|
||||
// Отправляем уведомление
|
||||
await this.bot.sendMessage(targetUser.telegram_id, message, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '👀 Посмотреть профиль', callback_data: `view_profile:${likerProfile.userId}` },
|
||||
{ text: '💕 Начать знакомиться', callback_data: 'start_browsing' }
|
||||
]]
|
||||
}
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: keyboard
|
||||
});
|
||||
|
||||
// Логируем уведомление
|
||||
await this.logNotification({
|
||||
userId: targetUser.id,
|
||||
type: isSuperLike ? 'super_like' : 'new_like',
|
||||
data: { likerUserId: likerProfile.userId, likerName: likerProfile.name }
|
||||
type: templateType,
|
||||
data: {
|
||||
likerUserId: likerProfile.userId,
|
||||
likerName: likerProfile.name,
|
||||
age: likerProfile.age,
|
||||
city: likerProfile.city
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sending like notification:', error);
|
||||
@@ -67,22 +202,50 @@ export class NotificationService {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = `🎉 У вас новый матч с ${matchedProfile.name}!\n\nТеперь вы можете начать общение.`;
|
||||
// Получаем матч-ID для перехода в чат
|
||||
const matchResult = await query(`
|
||||
SELECT id FROM matches
|
||||
WHERE (user_id_1 = $1 AND user_id_2 = $2) OR (user_id_1 = $2 AND user_id_2 = $1)
|
||||
AND is_active = true
|
||||
`, [userId, matchedUserId]);
|
||||
|
||||
const matchId = matchResult.rows[0]?.id;
|
||||
|
||||
// Получаем шаблон уведомления
|
||||
const template = await this.getNotificationTemplate('new_match');
|
||||
|
||||
// Подготовка данных для шаблона
|
||||
const templateData = {
|
||||
name: matchedProfile.name,
|
||||
age: matchedProfile.age.toString(),
|
||||
city: matchedProfile.city || '',
|
||||
userId: matchedProfile.userId,
|
||||
matchId: matchId || ''
|
||||
};
|
||||
|
||||
// Применяем данные к шаблону сообщения
|
||||
const message = this.applyTemplateData(template.messageTemplate, templateData);
|
||||
|
||||
// Применяем данные к шаблону кнопок
|
||||
const keyboard = this.applyTemplateDataToButtons(template.buttonTemplate, templateData);
|
||||
|
||||
// Отправляем уведомление
|
||||
await this.bot.sendMessage(user.telegram_id, message, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '💬 Написать сообщение', callback_data: `start_chat:${matchedUserId}` },
|
||||
{ text: '👀 Посмотреть профиль', callback_data: `view_profile:${matchedUserId}` }
|
||||
]]
|
||||
}
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: keyboard
|
||||
});
|
||||
|
||||
// Логируем уведомление
|
||||
await this.logNotification({
|
||||
userId,
|
||||
type: 'new_match',
|
||||
data: { matchedUserId, matchedName: matchedProfile.name }
|
||||
data: {
|
||||
matchedUserId,
|
||||
matchedName: matchedProfile.name,
|
||||
age: matchedProfile.age,
|
||||
city: matchedProfile.city,
|
||||
matchId
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sending match notification:', error);
|
||||
@@ -90,7 +253,7 @@ export class NotificationService {
|
||||
}
|
||||
|
||||
// Отправить уведомление о новом сообщении
|
||||
async sendMessageNotification(receiverId: string, senderId: string, messageContent: string): Promise<void> {
|
||||
async sendMessageNotification(receiverId: string, senderId: string, messageContent: string, matchId?: string): Promise<void> {
|
||||
try {
|
||||
const [receiver, senderProfile] = await Promise.all([
|
||||
this.getUserByUserId(receiverId),
|
||||
@@ -107,25 +270,55 @@ export class NotificationService {
|
||||
return; // Не отправляем уведомление, если пользователь активен в чате
|
||||
}
|
||||
|
||||
// Если matchId не передан, пытаемся его получить
|
||||
let actualMatchId = matchId;
|
||||
if (!actualMatchId) {
|
||||
const matchResult = await query(`
|
||||
SELECT id FROM matches
|
||||
WHERE (user_id_1 = $1 AND user_id_2 = $2) OR (user_id_1 = $2 AND user_id_2 = $1)
|
||||
AND is_active = true
|
||||
`, [receiverId, senderId]);
|
||||
|
||||
actualMatchId = matchResult.rows[0]?.id;
|
||||
}
|
||||
|
||||
const truncatedMessage = messageContent.length > 50
|
||||
? messageContent.substring(0, 50) + '...'
|
||||
: messageContent;
|
||||
|
||||
const message = `💬 Новое сообщение от ${senderProfile.name}:\n\n${truncatedMessage}`;
|
||||
|
||||
|
||||
// Получаем шаблон уведомления
|
||||
const template = await this.getNotificationTemplate('new_message');
|
||||
|
||||
// Подготовка данных для шаблона
|
||||
const templateData = {
|
||||
name: senderProfile.name,
|
||||
message: truncatedMessage,
|
||||
userId: senderProfile.userId,
|
||||
matchId: actualMatchId || ''
|
||||
};
|
||||
|
||||
// Применяем данные к шаблону сообщения
|
||||
const message = this.applyTemplateData(template.messageTemplate, templateData);
|
||||
|
||||
// Применяем данные к шаблону кнопок
|
||||
const keyboard = this.applyTemplateDataToButtons(template.buttonTemplate, templateData);
|
||||
|
||||
// Отправляем уведомление
|
||||
await this.bot.sendMessage(receiver.telegram_id, message, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '💬 Ответить', callback_data: `open_chat:${senderId}` }
|
||||
]]
|
||||
}
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: keyboard
|
||||
});
|
||||
|
||||
// Логируем уведомление
|
||||
await this.logNotification({
|
||||
userId: receiverId,
|
||||
type: 'new_message',
|
||||
data: { senderId, senderName: senderProfile.name, messageContent: truncatedMessage }
|
||||
data: {
|
||||
senderId,
|
||||
senderName: senderProfile.name,
|
||||
messageContent: truncatedMessage,
|
||||
matchId: actualMatchId
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sending message notification:', error);
|
||||
@@ -364,7 +557,7 @@ export class NotificationService {
|
||||
type VARCHAR(50) NOT NULL,
|
||||
data JSONB,
|
||||
scheduled_at TIMESTAMP NOT NULL,
|
||||
is_processed BOOLEAN DEFAULT FALSE,
|
||||
processed BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
@@ -373,7 +566,7 @@ export class NotificationService {
|
||||
// Получаем запланированные уведомления
|
||||
const result = await query(`
|
||||
SELECT * FROM scheduled_notifications
|
||||
WHERE scheduled_at <= $1 AND is_processed = false
|
||||
WHERE scheduled_at <= $1 AND processed = false
|
||||
ORDER BY scheduled_at ASC
|
||||
LIMIT 100
|
||||
`, [new Date()]);
|
||||
@@ -393,7 +586,7 @@ export class NotificationService {
|
||||
|
||||
// Отмечаем как обработанное
|
||||
await query(
|
||||
'UPDATE scheduled_notifications SET is_processed = true WHERE id = $1',
|
||||
'UPDATE scheduled_notifications SET processed = true WHERE id = $1',
|
||||
[notification.id]
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -404,4 +597,4 @@ export class NotificationService {
|
||||
console.error('Error processing scheduled notifications:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user