Files
sst_site/services/telegram.js
Andrey K. Choi 9477ff6de0 feat: Реализован полный CRUD для админ-панели и улучшена функциональность
- Portfolio CRUD: добавление, редактирование, удаление, переключение публикации
- Services CRUD: полное управление услугами с возможностью активации/деактивации
- Banner system: новая модель Banner с CRUD операциями и аналитикой кликов
- Telegram integration: расширенные настройки бота, обнаружение чатов, отправка сообщений
- Media management: улучшенная загрузка файлов с оптимизацией изображений и превью
- UI improvements: обновлённые админ-панели с rich-text редактором и drag&drop загрузкой
- Database: добавлена таблица banners с полями для баннеров и аналитики
2025-10-22 20:32:16 +09:00

374 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const axios = require('axios');
class TelegramService {
constructor() {
this.botToken = process.env.TELEGRAM_BOT_TOKEN;
this.chatId = process.env.TELEGRAM_CHAT_ID;
this.baseUrl = `https://api.telegram.org/bot${this.botToken}`;
this.isEnabled = !!(this.botToken && this.chatId);
this.chats = new Map(); // Store chat information
this.botInfo = null; // Store bot information
}
// Update bot token and reinitialize
updateBotToken(newToken) {
this.botToken = newToken;
this.baseUrl = `https://api.telegram.org/bot${this.botToken}`;
this.isEnabled = !!(this.botToken && this.chatId);
this.botInfo = null; // Reset bot info
return this.testConnection();
}
// Update default chat ID
updateChatId(newChatId) {
this.chatId = newChatId;
this.isEnabled = !!(this.botToken && this.chatId);
}
// Get bot information
async getBotInfo() {
if (this.botInfo) return { success: true, bot: this.botInfo };
if (!this.botToken) {
return { success: false, message: 'Bot token not configured' };
}
try {
const response = await axios.get(`${this.baseUrl}/getMe`);
this.botInfo = response.data.result;
return { success: true, bot: this.botInfo };
} catch (error) {
console.error('Get bot info error:', error.response?.data || error.message);
return { success: false, error: error.message };
}
}
// Get updates (for getting chat IDs and managing chats)
async getUpdates() {
if (!this.botToken) {
return { success: false, message: 'Bot token not configured' };
}
try {
const response = await axios.get(`${this.baseUrl}/getUpdates`);
const updates = response.data.result;
// Extract unique chats
const chats = new Map();
updates.forEach(update => {
if (update.message) {
const chat = update.message.chat;
chats.set(chat.id, {
id: chat.id,
type: chat.type,
title: chat.title || `${chat.first_name || ''} ${chat.last_name || ''}`.trim(),
username: chat.username || null,
first_name: chat.first_name || null,
last_name: chat.last_name || null,
description: chat.description || null,
invite_link: chat.invite_link || null,
pinned_message: chat.pinned_message || null,
permissions: chat.permissions || null,
slow_mode_delay: chat.slow_mode_delay || null
});
}
});
// Update internal chat storage
chats.forEach((chat, id) => {
this.chats.set(id, chat);
});
return {
success: true,
updates,
chats: Array.from(chats.values()),
totalUpdates: updates.length
};
} catch (error) {
console.error('Get updates error:', error.response?.data || error.message);
return { success: false, error: error.message };
}
}
// Get chat information
async getChat(chatId) {
if (!this.botToken) {
return { success: false, message: 'Bot token not configured' };
}
try {
const response = await axios.get(`${this.baseUrl}/getChat`, {
params: { chat_id: chatId }
});
const chat = response.data.result;
// Store in local cache
this.chats.set(chatId, chat);
return { success: true, chat };
} catch (error) {
console.error('Get chat error:', error.response?.data || error.message);
return { success: false, error: error.message };
}
}
// Get chat administrators
async getChatAdministrators(chatId) {
if (!this.botToken) {
return { success: false, message: 'Bot token not configured' };
}
try {
const response = await axios.get(`${this.baseUrl}/getChatAdministrators`, {
params: { chat_id: chatId }
});
return { success: true, administrators: response.data.result };
} catch (error) {
console.error('Get chat administrators error:', error.response?.data || error.message);
return { success: false, error: error.message };
}
}
// Get available chats (cached)
getAvailableChats() {
return Array.from(this.chats.values()).map(chat => ({
id: chat.id,
title: chat.title || chat.username || `${chat.first_name || ''} ${chat.last_name || ''}`.trim(),
type: chat.type,
username: chat.username
}));
}
async sendMessage(text, options = {}) {
const chatId = options.chat_id || this.chatId;
if (!this.botToken) {
console.warn('Telegram bot token is not configured');
return { success: false, message: 'Telegram bot token not configured' };
}
if (!chatId) {
console.warn('Telegram chat ID is not specified');
return { success: false, message: 'Telegram chat ID not specified' };
}
try {
const response = await axios.post(`${this.baseUrl}/sendMessage`, {
chat_id: chatId,
text: text,
parse_mode: 'HTML',
disable_web_page_preview: false,
disable_notification: false,
...options
});
return { success: true, data: response.data };
} catch (error) {
console.error('Telegram send message error:', error.response?.data || error.message);
return { success: false, error: error.message };
}
}
// Send message to multiple chats
async sendBroadcastMessage(text, chatIds = [], options = {}) {
if (!this.botToken) {
return { success: false, message: 'Telegram bot token not configured' };
}
if (!chatIds || chatIds.length === 0) {
return { success: false, message: 'No chat IDs specified' };
}
const results = [];
const errors = [];
for (const chatId of chatIds) {
try {
const result = await this.sendMessage(text, {
...options,
chat_id: chatId
});
if (result.success) {
results.push({ chatId, success: true, messageId: result.data.result.message_id });
} else {
errors.push({ chatId, error: result.error || result.message });
}
// Add delay between messages to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
errors.push({ chatId, error: error.message });
}
}
return {
success: errors.length === 0,
results,
errors,
totalSent: results.length,
totalFailed: errors.length
};
}
// Send custom message with advanced options
async sendCustomMessage({
text,
chatIds = [],
parseMode = 'HTML',
disableWebPagePreview = false,
disableNotification = false,
replyMarkup = null
}) {
const targetChats = chatIds.length > 0 ? chatIds : [this.chatId];
return await this.sendBroadcastMessage(text, targetChats, {
parse_mode: parseMode,
disable_web_page_preview: disableWebPagePreview,
disable_notification: disableNotification,
reply_markup: replyMarkup
});
}
async sendContactNotification(contact) {
const message = this.formatContactMessage(contact);
return await this.sendMessage(message);
}
async sendNewContactAlert(contact) {
const message = `🔔 <b>Новый запрос с сайта!</b>\n\n` +
`👤 <b>Клиент:</b> ${contact.name}\n` +
`📧 <b>Email:</b> ${contact.email}\n` +
`📱 <b>Телефон:</b> ${contact.phone || 'Не указан'}\n` +
`💼 <b>Услуга:</b> ${contact.serviceInterest || 'Общий запрос'}\n` +
`💰 <b>Бюджет:</b> ${contact.budget || 'Не указан'}\n` +
`⏱️ <b>Сроки:</b> ${contact.timeline || 'Не указаны'}\n\n` +
`💬 <b>Сообщение:</b>\n${contact.message}\n\n` +
`🕐 <b>Время:</b> ${new Date(contact.createdAt).toLocaleString('ru-RU')}\n\n` +
`🔗 <a href="${process.env.BASE_URL || 'http://localhost:3000'}/admin/contacts/${contact.id}">Открыть в админ-панели</a>`;
return await this.sendMessage(message);
}
async sendPortfolioNotification(portfolio) {
const message = `📁 <b>Новый проект добавлен в портфолио</b>\n\n` +
`🏷️ <b>Название:</b> ${portfolio.title}\n` +
`📂 <b>Категория:</b> ${portfolio.category}\n` +
`👤 <b>Клиент:</b> ${portfolio.clientName || 'Не указан'}\n` +
`🌐 <b>URL:</b> ${portfolio.projectUrl || 'Не указан'}\n` +
`⭐ <b>Рекомендуемый:</b> ${portfolio.featured ? 'Да' : 'Нет'}\n` +
`📅 <b>Время:</b> ${new Date(portfolio.createdAt).toLocaleString('ru-RU')}\n\n` +
`🔗 <a href="${process.env.BASE_URL || 'http://localhost:3000'}/portfolio/${portfolio.id}">Посмотреть проект</a>`;
return await this.sendMessage(message);
}
async sendServiceNotification(service) {
const message = `⚙️ <b>Новая услуга добавлена</b>\n\n` +
`🏷️ <b>Название:</b> ${service.name}\n` +
`📂 <b>Категория:</b> ${service.category}\n` +
`💰 <b>Стоимость:</b> ${service.pricing?.basePrice ? `от $${service.pricing.basePrice}` : 'По запросу'}\n` +
`⏱️ <b>Время выполнения:</b> ${service.estimatedTime || 'Не указано'}\n` +
`⭐ <b>Рекомендуемая:</b> ${service.featured ? 'Да' : 'Нет'}\n` +
`📅 <b>Время:</b> ${new Date(service.createdAt).toLocaleString('ru-RU')}\n\n` +
`🔗 <a href="${process.env.BASE_URL || 'http://localhost:3000'}/services">Посмотреть услуги</a>`;
return await this.sendMessage(message);
}
async sendCalculatorQuote(calculatorData) {
const totalCost = calculatorData.services?.reduce((sum, service) => sum + (service.price || 0), 0) || 0;
const message = `💰 <b>Новый расчет стоимости</b>\n\n` +
`👤 <b>Клиент:</b> ${calculatorData.name || 'Не указан'}\n` +
`📧 <b>Email:</b> ${calculatorData.email || 'Не указан'}\n` +
`📱 <b>Телефон:</b> ${calculatorData.phone || 'Не указан'}\n\n` +
`🛠️ <b>Выбранные услуги:</b>\n${this.formatServices(calculatorData.services)}\n` +
`💵 <b>Общая стоимость:</b> $${totalCost}\n\n` +
`📅 <b>Время:</b> ${new Date().toLocaleString('ru-RU')}`;
return await this.sendMessage(message);
}
formatContactMessage(contact) {
return `📞 <b>Уведомление о контакте</b>\n\n` +
`👤 <b>Клиент:</b> ${contact.name}\n` +
`📧 <b>Email:</b> ${contact.email}\n` +
`📱 <b>Телефон:</b> ${contact.phone || 'Не указан'}\n` +
`💼 <b>Услуга:</b> ${contact.serviceInterest || 'Общий запрос'}\n` +
`📊 <b>Статус:</b> ${this.getStatusText(contact.status)}\n` +
`⚡ <b>Приоритет:</b> ${this.getPriorityText(contact.priority)}\n\n` +
`💬 <b>Сообщение:</b>\n${contact.message}\n\n` +
`🔗 <a href="${process.env.BASE_URL || 'http://localhost:3000'}/admin/contacts/${contact.id}">Открыть в админ-панели</a>`;
}
formatServices(services) {
if (!services || services.length === 0) return 'Не выбрано';
return services.map(service =>
`${service.name} - $${service.price || 0}`
).join('\n');
}
getStatusText(status) {
const statusMap = {
'new': '🆕 Новое',
'in_progress': '⏳ В работе',
'completed': '✅ Завершено'
};
return statusMap[status] || status;
}
getPriorityText(priority) {
const priorityMap = {
'low': '🟢 Низкий',
'medium': '🟡 Средний',
'high': '🔴 Высокий'
};
return priorityMap[priority] || priority;
}
// Test connection and update bot info
async testConnection() {
if (!this.botToken) {
return { success: false, message: 'Telegram bot token not configured' };
}
try {
const response = await axios.get(`${this.baseUrl}/getMe`);
this.botInfo = response.data.result;
// Also get updates to discover available chats
await this.getUpdates();
return {
success: true,
bot: this.botInfo,
availableChats: this.getAvailableChats()
};
} catch (error) {
console.error('Telegram connection test error:', error.response?.data || error.message);
return { success: false, error: error.message };
}
}
// Webhook setup (for future use)
async setWebhook(webhookUrl) {
if (!this.isEnabled) {
return { success: false, message: 'Telegram bot not configured' };
}
try {
const response = await axios.post(`${this.baseUrl}/setWebhook`, {
url: webhookUrl
});
return { success: true, data: response.data };
} catch (error) {
console.error('Telegram webhook setup error:', error.response?.data || error.message);
return { success: false, error: error.message };
}
}
}
module.exports = new TelegramService();