const axios = require('axios'); const config = require('../config'); const { log, logError } = require('../middleware/logger'); const User = require('../models/User'); const BOT_TOKEN = config.telegramBotToken; const TELEGRAM_API = BOT_TOKEN ? `https://api.telegram.org/bot${BOT_TOKEN}` : null; let isPolling = false; let offset = 0; const sendMessage = async (chatId, text, options = {}) => { if (!TELEGRAM_API) { log('warn', 'TELEGRAM_BOT_TOKEN не установлен, отправка сообщения невозможна'); return null; } try { const response = await axios.post(`${TELEGRAM_API}/sendMessage`, { chat_id: chatId, text, parse_mode: 'HTML', ...options }); return response.data; } catch (error) { logError('Ошибка отправки сообщения', error, { chatId, text: text.substring(0, 50) }); return null; } }; const sendMessageToAllUsers = async (messageText) => { if (!TELEGRAM_API) { throw new Error('TELEGRAM_BOT_TOKEN не установлен'); } try { const users = await User.find({ banned: { $ne: true } }).select('telegramId'); let sent = 0; let failed = 0; for (const user of users) { try { await sendMessage(user.telegramId, messageText); sent++; // Небольшая задержка, чтобы не превысить лимиты API await new Promise(resolve => setTimeout(resolve, 50)); } catch (error) { failed++; logError('Ошибка отправки сообщения пользователю', error, { telegramId: user.telegramId }); } } return { sent, failed, total: users.length }; } catch (error) { logError('Ошибка массовой отправки сообщений', error); throw error; } }; const getStartMessage = () => { return `👋 Добро пожаловать в Nakama! 📱 Nakama — социальная сеть для фурри и аниме сообщества. Основные возможности: • Создание постов с текстом и изображениями • Поиск контента через e621 и Gelbooru • Комментарии и лайки • Подписки на пользователей • Система уведомлений • Фильтры и теги Как начать: 1. Нажмите кнопку "Войти" ниже, чтобы запустить приложение 2. Создайте свой первый пост 3. Подписывайтесь на интересных пользователей Поддержка: Если возникли проблемы, напишите @NakamaReportbot Приятного использования!`; }; const handleCommand = async (message) => { const chatId = message.chat.id; const text = (message.text || '').trim(); const args = text.split(/\s+/); const command = args[0].toLowerCase(); if (command === '/start') { const startParam = message.text.split(' ')[1] || ''; // Если есть start_param (например, post_12345 или ref_ABC123) // Это обрабатывается при открытии миниаппа, здесь просто показываем инструкцию const startMessage = getStartMessage(); // Добавить кнопку для открытия миниаппа let botUsername = 'NakamaSpaceBot'; if (config.telegramBotToken) { try { const botInfo = await axios.get(`${TELEGRAM_API}/getMe`); botUsername = botInfo.data.result.username || 'NakamaSpaceBot'; } catch (error) { log('warn', 'Не удалось получить имя бота', { error: error.message }); } } // Использовать web_app с правильным URL миниаппа const miniappUrl = 'https://nakama.glpshchn.ru/'; await sendMessage(chatId, startMessage, { reply_markup: { inline_keyboard: [[ { text: '🚀 Открыть Nakama', web_app: { url: miniappUrl } } ]] } }); return; } // Игнорируем неизвестные команды }; const processUpdate = async (update) => { const message = update.message || update.edited_message; if (!message || !message.text) { return; } try { await handleCommand(message); } catch (error) { logError('Ошибка обработки команды основного бота', error, { chatId: message.chat.id, text: message.text?.substring(0, 50) }); } }; const pollUpdates = async () => { if (!TELEGRAM_API) { log('warn', 'Основной бот не запущен: TELEGRAM_BOT_TOKEN не установлен'); return; } if (isPolling) { return; } isPolling = true; log('info', 'Основной бот запущен, опрос обновлений...'); // При первом запуске получить все обновления и установить offset на последний, // чтобы не отвечать на старые команды const initializeOffset = async () => { try { const response = await axios.get(`${TELEGRAM_API}/getUpdates`, { params: { timeout: 1, allowed_updates: ['message'] } }); const updates = response.data.result || []; if (updates.length > 0) { // Установить offset на последний update_id + 1, чтобы пропустить все старые обновления offset = updates[updates.length - 1].update_id + 1; log('info', `Пропущено ${updates.length} старых обновлений, offset установлен на ${offset}`); } } catch (error) { log('warn', 'Не удалось инициализировать offset, начнем с 0', { error: error.message }); } }; const poll = async () => { try { const response = await axios.get(`${TELEGRAM_API}/getUpdates`, { params: { offset, timeout: 30, allowed_updates: ['message'] } }); const updates = response.data.result || []; for (const update of updates) { offset = update.update_id + 1; await processUpdate(update); } // Продолжить опрос setTimeout(poll, 1000); } catch (error) { const errorData = error.response?.data || {}; const errorCode = errorData.error_code; const errorDescription = errorData.description || error.message; // Обработка конфликта 409 - другой экземпляр бота уже опрашивает if (errorCode === 409) { // Не логируем 409 - это ожидаемая ситуация при конфликте экземпляров // Подождать дольше перед повторной попыткой setTimeout(poll, 10000); } else { logError('Ошибка опроса Telegram для основного бота', error); // Переподключиться через 5 секунд setTimeout(poll, 5000); } } }; // Сначала инициализировать offset, затем начать опрос initializeOffset().then(() => { poll(); }); }; const startMainBot = () => { if (!BOT_TOKEN) { log('warn', 'Основной бот не запущен: TELEGRAM_BOT_TOKEN не установлен'); return; } pollUpdates(); }; module.exports = { startMainBot, sendMessageToAllUsers, sendMessage };