init commit
This commit is contained in:
334
src/services/notificationService.ts
Normal file
334
src/services/notificationService.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
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;
|
||||
type: 'new_match' | 'new_message' | 'new_like' | 'super_like';
|
||||
data: Record<string, any>;
|
||||
scheduledAt?: Date;
|
||||
}
|
||||
|
||||
export class NotificationService {
|
||||
private bot?: TelegramBot;
|
||||
private profileService: ProfileService;
|
||||
|
||||
constructor(bot?: TelegramBot) {
|
||||
this.bot = bot;
|
||||
this.profileService = new ProfileService();
|
||||
}
|
||||
|
||||
// Отправить уведомление о новом лайке
|
||||
async sendLikeNotification(targetTelegramId: string, likerTelegramId: string, isSuperLike: boolean = false): Promise<void> {
|
||||
try {
|
||||
const [targetUser, likerProfile] = await Promise.all([
|
||||
this.getUserByTelegramId(targetTelegramId),
|
||||
this.profileService.getProfileByTelegramId(likerTelegramId)
|
||||
]);
|
||||
|
||||
if (!targetUser || !likerProfile || !this.bot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = isSuperLike
|
||||
? `⭐ ${likerProfile.name} отправил вам суперлайк!`
|
||||
: `💖 ${likerProfile.name} поставил вам лайк!`;
|
||||
|
||||
await this.bot.sendMessage(targetUser.telegram_id, message, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '👀 Посмотреть профиль', callback_data: `view_profile:${likerProfile.userId}` },
|
||||
{ text: '💕 Начать знакомиться', callback_data: 'start_browsing' }
|
||||
]]
|
||||
}
|
||||
});
|
||||
|
||||
// Логируем уведомление
|
||||
await this.logNotification({
|
||||
userId: targetUser.id,
|
||||
type: isSuperLike ? 'super_like' : 'new_like',
|
||||
data: { likerUserId: likerProfile.userId, likerName: likerProfile.name }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sending like notification:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Отправить уведомление о новом матче
|
||||
async sendMatchNotification(userId: string, matchedUserId: string): Promise<void> {
|
||||
try {
|
||||
const [user, matchedProfile] = await Promise.all([
|
||||
this.getUserByUserId(userId),
|
||||
this.profileService.getProfileByUserId(matchedUserId)
|
||||
]);
|
||||
|
||||
if (!user || !matchedProfile || !this.bot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = `🎉 У вас новый матч с ${matchedProfile.name}!\n\nТеперь вы можете начать общение.`;
|
||||
|
||||
await this.bot.sendMessage(user.telegram_id, message, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '💬 Написать сообщение', callback_data: `start_chat:${matchedUserId}` },
|
||||
{ text: '👀 Посмотреть профиль', callback_data: `view_profile:${matchedUserId}` }
|
||||
]]
|
||||
}
|
||||
});
|
||||
|
||||
// Логируем уведомление
|
||||
await this.logNotification({
|
||||
userId,
|
||||
type: 'new_match',
|
||||
data: { matchedUserId, matchedName: matchedProfile.name }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sending match notification:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Отправить уведомление о новом сообщении
|
||||
async sendMessageNotification(receiverId: string, senderId: string, messageContent: string): Promise<void> {
|
||||
try {
|
||||
const [receiver, senderProfile] = await Promise.all([
|
||||
this.getUserByUserId(receiverId),
|
||||
this.profileService.getProfileByUserId(senderId)
|
||||
]);
|
||||
|
||||
if (!receiver || !senderProfile || !this.bot) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, не в чате ли пользователь сейчас
|
||||
const isUserActive = await this.isUserActiveInChat(receiverId, senderId);
|
||||
if (isUserActive) {
|
||||
return; // Не отправляем уведомление, если пользователь активен в чате
|
||||
}
|
||||
|
||||
const truncatedMessage = messageContent.length > 50
|
||||
? messageContent.substring(0, 50) + '...'
|
||||
: messageContent;
|
||||
|
||||
const message = `💬 Новое сообщение от ${senderProfile.name}:\n\n${truncatedMessage}`;
|
||||
|
||||
await this.bot.sendMessage(receiver.telegram_id, message, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '💬 Ответить', callback_data: `open_chat:${senderId}` }
|
||||
]]
|
||||
}
|
||||
});
|
||||
|
||||
// Логируем уведомление
|
||||
await this.logNotification({
|
||||
userId: receiverId,
|
||||
type: 'new_message',
|
||||
data: { senderId, senderName: senderProfile.name, messageContent: truncatedMessage }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sending message notification:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Отправить напоминание о неактивности
|
||||
async sendInactivityReminder(userId: string): Promise<void> {
|
||||
try {
|
||||
const user = await this.getUserByUserId(userId);
|
||||
if (!user || !this.bot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = `👋 Давно не виделись!\n\nВозможно, ваш идеальный матч уже ждет. Давайте найдем кого-то особенного?`;
|
||||
|
||||
await this.bot.sendMessage(user.telegram_id, message, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '💕 Начать знакомиться', callback_data: 'start_browsing' },
|
||||
{ text: '⚙️ Настройки', callback_data: 'settings' }
|
||||
]]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sending inactivity reminder:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Отправить уведомление о новых лайках (сводка)
|
||||
async sendLikesSummary(userId: string, likesCount: number): Promise<void> {
|
||||
try {
|
||||
const user = await this.getUserByUserId(userId);
|
||||
if (!user || !this.bot || likesCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = likesCount === 1
|
||||
? `💖 У вас 1 новый лайк! Посмотрите, кто это может быть.`
|
||||
: `💖 У вас ${likesCount} новых лайков! Посмотрите, кто проявил к вам интерес.`;
|
||||
|
||||
await this.bot.sendMessage(user.telegram_id, message, {
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '👀 Посмотреть лайки', callback_data: 'view_likes' },
|
||||
{ text: '💕 Начать знакомиться', callback_data: 'start_browsing' }
|
||||
]]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error sending likes summary:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Логирование уведомлений
|
||||
private async logNotification(notificationData: NotificationData): Promise<void> {
|
||||
try {
|
||||
await query(`
|
||||
INSERT INTO notifications (user_id, type, data, created_at)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`, [
|
||||
notificationData.userId,
|
||||
notificationData.type,
|
||||
JSON.stringify(notificationData.data),
|
||||
new Date()
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('Error logging notification:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Получить пользователя по ID
|
||||
private async getUserByUserId(userId: string): Promise<any> {
|
||||
try {
|
||||
const result = await query(
|
||||
'SELECT * FROM users WHERE id = $1',
|
||||
[userId]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
console.error('Error getting user:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Получить пользователя по Telegram ID
|
||||
private async getUserByTelegramId(telegramId: string): Promise<any> {
|
||||
try {
|
||||
const result = await query(
|
||||
'SELECT * FROM users WHERE telegram_id = $1',
|
||||
[parseInt(telegramId)]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
console.error('Error getting user by telegram ID:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Проверить, активен ли пользователь в чате
|
||||
private async isUserActiveInChat(userId: string, chatWithUserId: string): Promise<boolean> {
|
||||
// TODO: Реализовать проверку активности пользователя
|
||||
// Можно использовать Redis для хранения состояния активности
|
||||
return false;
|
||||
}
|
||||
|
||||
// Отправить пуш-уведомление (для будущего использования)
|
||||
async sendPushNotification(userId: string, title: string, body: string, data?: any): Promise<void> {
|
||||
// TODO: Интеграция с Firebase Cloud Messaging или другим сервисом пуш-уведомлений
|
||||
console.log(`Push notification for ${userId}: ${title} - ${body}`);
|
||||
}
|
||||
|
||||
// Получить настройки уведомлений пользователя
|
||||
async getNotificationSettings(userId: string): Promise<{
|
||||
newMatches: boolean;
|
||||
newMessages: boolean;
|
||||
newLikes: boolean;
|
||||
reminders: boolean;
|
||||
}> {
|
||||
try {
|
||||
const result = await query(
|
||||
'SELECT notification_settings FROM users WHERE id = $1',
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return {
|
||||
newMatches: true,
|
||||
newMessages: true,
|
||||
newLikes: true,
|
||||
reminders: true
|
||||
};
|
||||
}
|
||||
|
||||
return result.rows[0].notification_settings || {
|
||||
newMatches: true,
|
||||
newMessages: true,
|
||||
newLikes: true,
|
||||
reminders: true
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting notification settings:', error);
|
||||
return {
|
||||
newMatches: true,
|
||||
newMessages: true,
|
||||
newLikes: true,
|
||||
reminders: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Обновить настройки уведомлений
|
||||
async updateNotificationSettings(userId: string, settings: {
|
||||
newMatches?: boolean;
|
||||
newMessages?: boolean;
|
||||
newLikes?: boolean;
|
||||
reminders?: boolean;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await query(
|
||||
'UPDATE users SET notification_settings = $1 WHERE id = $2',
|
||||
[JSON.stringify(settings), userId]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error updating notification settings:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Планировщик уведомлений (вызывается периодически)
|
||||
async processScheduledNotifications(): Promise<void> {
|
||||
try {
|
||||
// Получаем запланированные уведомления
|
||||
const result = await query(`
|
||||
SELECT * FROM scheduled_notifications
|
||||
WHERE scheduled_at <= $1 AND sent = false
|
||||
ORDER BY scheduled_at ASC
|
||||
LIMIT 100
|
||||
`, [new Date()]);
|
||||
|
||||
for (const notification of result.rows) {
|
||||
try {
|
||||
switch (notification.type) {
|
||||
case 'inactivity_reminder':
|
||||
await this.sendInactivityReminder(notification.user_id);
|
||||
break;
|
||||
case 'likes_summary':
|
||||
const likesCount = notification.data?.likesCount || 0;
|
||||
await this.sendLikesSummary(notification.user_id, likesCount);
|
||||
break;
|
||||
// Добавить другие типы уведомлений
|
||||
}
|
||||
|
||||
// Отмечаем как отправленное
|
||||
await query(
|
||||
'UPDATE scheduled_notifications SET sent = true, sent_at = $1 WHERE id = $2',
|
||||
[new Date(), notification.id]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error processing notification ${notification.id}:`, error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing scheduled notifications:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user