2025-12-04 17:44:05 +00:00
|
|
|
|
const axios = require('axios');
|
|
|
|
|
|
const config = require('../config');
|
|
|
|
|
|
const { log, logError } = require('../middleware/logger');
|
|
|
|
|
|
const User = require('../models/User');
|
2025-12-07 15:19:22 +00:00
|
|
|
|
const { normalizeUsername } = require('../services/moderationAdmin');
|
|
|
|
|
|
|
|
|
|
|
|
const OWNER_USERNAMES = new Set(config.moderationOwnerUsernames || []);
|
2025-12-04 17:44:05 +00:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-07 15:19:22 +00:00
|
|
|
|
const requireAdmin = async (message) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const telegramId = message.from?.id;
|
|
|
|
|
|
if (!telegramId) return false;
|
|
|
|
|
|
|
|
|
|
|
|
const user = await User.findOne({ telegramId }).select('role');
|
|
|
|
|
|
const username = normalizeUsername(message.from?.username || '');
|
|
|
|
|
|
|
|
|
|
|
|
return user && (user.role === 'admin' || OWNER_USERNAMES.has(username));
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logError('Ошибка проверки прав админа', error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-04 17:44:05 +00:00
|
|
|
|
const getStartMessage = () => {
|
2025-12-07 02:36:30 +00:00
|
|
|
|
return `<b>Добро пожаловать в Nakama!</b>
|
2025-12-04 17:44:05 +00:00
|
|
|
|
|
2025-12-07 02:36:30 +00:00
|
|
|
|
📱 <b>Nakama</b> - социальная сеть для фурри и аниме сообщества.
|
2025-12-04 17:44:05 +00:00
|
|
|
|
|
|
|
|
|
|
<b>Основные возможности:</b>
|
|
|
|
|
|
• Создание постов с текстом и изображениями
|
|
|
|
|
|
• Поиск контента через e621 и Gelbooru
|
|
|
|
|
|
• Комментарии и лайки
|
|
|
|
|
|
• Подписки на пользователей
|
|
|
|
|
|
• Система уведомлений
|
|
|
|
|
|
• Фильтры и теги
|
|
|
|
|
|
|
|
|
|
|
|
<b>Как начать:</b>
|
|
|
|
|
|
1. Нажмите кнопку "Войти" ниже, чтобы запустить приложение
|
|
|
|
|
|
2. Создайте свой первый пост
|
|
|
|
|
|
3. Подписывайтесь на интересных пользователей
|
|
|
|
|
|
|
|
|
|
|
|
<b>Поддержка:</b>
|
|
|
|
|
|
Если возникли проблемы, напишите @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 });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 18:02:36 +00:00
|
|
|
|
// Использовать web_app с правильным URL миниаппа
|
|
|
|
|
|
const miniappUrl = 'https://nakama.glpshchn.ru/';
|
2025-12-04 17:44:05 +00:00
|
|
|
|
await sendMessage(chatId, startMessage, {
|
|
|
|
|
|
reply_markup: {
|
|
|
|
|
|
inline_keyboard: [[
|
|
|
|
|
|
{
|
|
|
|
|
|
text: '🚀 Открыть Nakama',
|
|
|
|
|
|
web_app: {
|
2025-12-04 18:02:36 +00:00
|
|
|
|
url: miniappUrl
|
2025-12-04 17:44:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]]
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-07 15:19:22 +00:00
|
|
|
|
if (command === '/broadcast') {
|
|
|
|
|
|
// Проверка прав
|
|
|
|
|
|
const isAdmin = await requireAdmin(message);
|
|
|
|
|
|
if (!isAdmin) {
|
|
|
|
|
|
await sendMessage(chatId, '❌ Команда доступна только администраторам.');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Получить текст сообщения (все после /broadcast)
|
|
|
|
|
|
const messageText = args.slice(1).join(' ').trim();
|
|
|
|
|
|
|
|
|
|
|
|
if (!messageText) {
|
|
|
|
|
|
await sendMessage(chatId, 'Использование: /broadcast <сообщение>\n\nПример: /broadcast Всем привет!');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Отправить подтверждение
|
|
|
|
|
|
await sendMessage(chatId, '📤 Начинаю рассылку сообщения всем пользователям...');
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = await sendMessageToAllUsers(messageText);
|
|
|
|
|
|
await sendMessage(chatId,
|
|
|
|
|
|
`✅ Рассылка завершена!\n\n` +
|
|
|
|
|
|
`📊 Статистика:\n` +
|
|
|
|
|
|
`• Отправлено: ${result.sent}\n` +
|
|
|
|
|
|
`• Ошибок: ${result.failed}\n` +
|
|
|
|
|
|
`• Всего пользователей: ${result.total}`
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logError('Ошибка рассылки через команду', error);
|
|
|
|
|
|
await sendMessage(chatId, `❌ Ошибка рассылки: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 17:44:05 +00:00
|
|
|
|
// Игнорируем неизвестные команды
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-07 22:15:00 +00:00
|
|
|
|
const handleInlineQuery = async (inlineQuery) => {
|
|
|
|
|
|
if (!TELEGRAM_API) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const query = inlineQuery.query || '';
|
|
|
|
|
|
const queryId = inlineQuery.id;
|
|
|
|
|
|
|
|
|
|
|
|
// Формат: "furry tag1 tag2" или "anime tag1 tag2"
|
|
|
|
|
|
const parts = query.trim().split(/\s+/).filter(p => p.length > 0);
|
|
|
|
|
|
|
|
|
|
|
|
if (parts.length === 0) {
|
|
|
|
|
|
// Если нет запроса, вернуть пустой результат
|
|
|
|
|
|
await axios.post(`${TELEGRAM_API}/answerInlineQuery`, {
|
|
|
|
|
|
inline_query_id: queryId,
|
|
|
|
|
|
results: []
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Первое слово - источник (furry или anime)
|
|
|
|
|
|
const source = parts[0].toLowerCase();
|
|
|
|
|
|
const tags = parts.slice(1);
|
|
|
|
|
|
|
|
|
|
|
|
if (source !== 'furry' && source !== 'anime') {
|
|
|
|
|
|
// Если первый параметр не furry или anime, вернуть пустой результат
|
|
|
|
|
|
await axios.post(`${TELEGRAM_API}/answerInlineQuery`, {
|
|
|
|
|
|
inline_query_id: queryId,
|
|
|
|
|
|
results: []
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (tags.length === 0) {
|
|
|
|
|
|
// Если нет тегов, вернуть пустой результат
|
|
|
|
|
|
await axios.post(`${TELEGRAM_API}/answerInlineQuery`, {
|
|
|
|
|
|
inline_query_id: queryId,
|
|
|
|
|
|
results: []
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Объединить теги в строку для поиска
|
|
|
|
|
|
const tagsQuery = tags.join(' ');
|
|
|
|
|
|
|
|
|
|
|
|
let searchResults = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (source === 'furry') {
|
|
|
|
|
|
// Поиск через e621 API
|
|
|
|
|
|
try {
|
|
|
|
|
|
const config = require('../config');
|
2025-12-07 22:58:38 +00:00
|
|
|
|
|
|
|
|
|
|
// Проверить наличие API ключа
|
|
|
|
|
|
if (!config.e621ApiKey) {
|
|
|
|
|
|
log('warn', 'e621 API ключ не настроен для inline query');
|
|
|
|
|
|
await axios.post(`${TELEGRAM_API}/answerInlineQuery`, {
|
|
|
|
|
|
inline_query_id: queryId,
|
|
|
|
|
|
results: [],
|
|
|
|
|
|
cache_time: 60
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-07 22:15:00 +00:00
|
|
|
|
const E621_USER_AGENT = 'NakamaApp/1.0 (by glpshchn00 on e621; Telegram: @glpshchn00)';
|
2025-12-07 22:58:38 +00:00
|
|
|
|
|
|
|
|
|
|
// e621 использует Basic Auth с username:api_key
|
|
|
|
|
|
// Если username не указан, используем API ключ как username и пустой пароль
|
|
|
|
|
|
// Или можно использовать формат :api_key (пустой username)
|
|
|
|
|
|
const username = config.e621Username || config.e621ApiKey;
|
|
|
|
|
|
const auth = Buffer.from(`${username}:${config.e621ApiKey}`).toString('base64');
|
2025-12-07 22:15:00 +00:00
|
|
|
|
|
|
|
|
|
|
const response = await axios.get('https://e621.net/posts.json', {
|
|
|
|
|
|
params: {
|
|
|
|
|
|
tags: tagsQuery,
|
|
|
|
|
|
limit: 50
|
|
|
|
|
|
},
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'User-Agent': E621_USER_AGENT,
|
|
|
|
|
|
'Authorization': `Basic ${auth}`
|
|
|
|
|
|
},
|
2025-12-07 22:58:38 +00:00
|
|
|
|
timeout: 10000,
|
|
|
|
|
|
validateStatus: (status) => status < 500 // Не бросать ошибку для 401/429
|
2025-12-07 22:15:00 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-12-07 22:58:38 +00:00
|
|
|
|
// Обработка ошибок авторизации
|
|
|
|
|
|
if (response.status === 401) {
|
|
|
|
|
|
log('warn', 'e621 API вернул 401 - неверные учетные данные для inline query');
|
|
|
|
|
|
await axios.post(`${TELEGRAM_API}/answerInlineQuery`, {
|
|
|
|
|
|
inline_query_id: queryId,
|
|
|
|
|
|
results: [],
|
|
|
|
|
|
cache_time: 60
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Обработка rate limit
|
|
|
|
|
|
if (response.status === 429) {
|
|
|
|
|
|
log('warn', 'e621 rate limit (429) для inline query');
|
|
|
|
|
|
await axios.post(`${TELEGRAM_API}/answerInlineQuery`, {
|
|
|
|
|
|
inline_query_id: queryId,
|
|
|
|
|
|
results: [],
|
|
|
|
|
|
cache_time: 60
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-07 22:15:00 +00:00
|
|
|
|
let postsData = [];
|
|
|
|
|
|
if (Array.isArray(response.data)) {
|
|
|
|
|
|
postsData = response.data;
|
|
|
|
|
|
} else if (response.data && Array.isArray(response.data.posts)) {
|
|
|
|
|
|
postsData = response.data.posts;
|
|
|
|
|
|
} else if (response.data && Array.isArray(response.data.data)) {
|
|
|
|
|
|
postsData = response.data.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
searchResults = postsData
|
|
|
|
|
|
.filter(post => post && post.file && post.file.url)
|
|
|
|
|
|
.slice(0, 50)
|
|
|
|
|
|
.map(post => ({
|
|
|
|
|
|
id: post.id,
|
|
|
|
|
|
url: post.file.url,
|
|
|
|
|
|
preview: post.preview && post.preview.url ? post.preview.url : post.file.url,
|
|
|
|
|
|
tags: post.tags && post.tags.general ? post.tags.general : [],
|
|
|
|
|
|
source: 'e621'
|
|
|
|
|
|
}));
|
|
|
|
|
|
} catch (error) {
|
2025-12-07 22:58:38 +00:00
|
|
|
|
// Логировать только если это не 401/429 (они уже обработаны выше)
|
|
|
|
|
|
if (error.response?.status !== 401 && error.response?.status !== 429) {
|
|
|
|
|
|
logError('Ошибка поиска e621 для inline query', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Вернуть пустой результат вместо падения
|
|
|
|
|
|
searchResults = [];
|
2025-12-07 22:15:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
} else if (source === 'anime') {
|
|
|
|
|
|
// Поиск через Gelbooru API
|
|
|
|
|
|
try {
|
|
|
|
|
|
const config = require('../config');
|
|
|
|
|
|
const response = await axios.get('https://gelbooru.com/index.php', {
|
|
|
|
|
|
params: {
|
|
|
|
|
|
page: 'dapi',
|
|
|
|
|
|
s: 'post',
|
|
|
|
|
|
q: 'index',
|
|
|
|
|
|
json: 1,
|
|
|
|
|
|
tags: tagsQuery,
|
|
|
|
|
|
limit: 50,
|
|
|
|
|
|
api_key: config.gelbooruApiKey,
|
|
|
|
|
|
user_id: config.gelbooruUserId
|
|
|
|
|
|
},
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
|
|
|
|
|
},
|
|
|
|
|
|
timeout: 10000
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
let postsData = [];
|
|
|
|
|
|
if (Array.isArray(response.data)) {
|
|
|
|
|
|
postsData = response.data;
|
|
|
|
|
|
} else if (response.data && response.data.post) {
|
|
|
|
|
|
postsData = Array.isArray(response.data.post) ? response.data.post : [response.data.post];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
searchResults = postsData
|
|
|
|
|
|
.filter(post => post && post.file_url)
|
|
|
|
|
|
.slice(0, 50)
|
|
|
|
|
|
.map(post => ({
|
|
|
|
|
|
id: post.id,
|
|
|
|
|
|
url: post.file_url,
|
|
|
|
|
|
preview: post.preview_url || post.thumbnail_url || post.file_url,
|
|
|
|
|
|
tags: post.tags ? (typeof post.tags === 'string' ? post.tags.split(' ') : post.tags) : [],
|
|
|
|
|
|
source: 'gelbooru'
|
|
|
|
|
|
}));
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logError('Ошибка поиска Gelbooru для inline query', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Получить username бота
|
|
|
|
|
|
let botUsername = 'NakamaSpaceBot';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const botInfo = await axios.get(`${TELEGRAM_API}/getMe`);
|
|
|
|
|
|
botUsername = botInfo.data.result.username || 'NakamaSpaceBot';
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
log('warn', 'Не удалось получить имя бота для inline query', { error: error.message });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Преобразовать результаты в InlineQueryResult
|
|
|
|
|
|
const results = searchResults.map((post, index) => {
|
|
|
|
|
|
const tagsStr = Array.isArray(post.tags) ? post.tags.slice(0, 10).join(' ') : '';
|
|
|
|
|
|
let caption = '';
|
|
|
|
|
|
|
|
|
|
|
|
if (tagsStr) {
|
|
|
|
|
|
caption = `Tags: ${tagsStr}\n\nvia @${botUsername}`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
caption = `via @${botUsername}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
type: 'photo',
|
|
|
|
|
|
id: `${post.source}_${post.id}_${index}`,
|
|
|
|
|
|
photo_url: post.url,
|
|
|
|
|
|
thumb_url: post.preview || post.url,
|
|
|
|
|
|
caption: caption.substring(0, 1024),
|
|
|
|
|
|
parse_mode: 'HTML'
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
await axios.post(`${TELEGRAM_API}/answerInlineQuery`, {
|
|
|
|
|
|
inline_query_id: queryId,
|
|
|
|
|
|
results: results,
|
|
|
|
|
|
cache_time: 300 // 5 минут
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logError('Ошибка обработки inline query', error);
|
|
|
|
|
|
// Отправить пустой результат при ошибке
|
|
|
|
|
|
try {
|
|
|
|
|
|
await axios.post(`${TELEGRAM_API}/answerInlineQuery`, {
|
|
|
|
|
|
inline_query_id: inlineQuery.id,
|
|
|
|
|
|
results: []
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// Игнорировать ошибку отправки пустого результата
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-04 17:44:05 +00:00
|
|
|
|
const processUpdate = async (update) => {
|
2025-12-07 22:15:00 +00:00
|
|
|
|
// Обработка inline query
|
|
|
|
|
|
if (update.inline_query) {
|
|
|
|
|
|
await handleInlineQuery(update.inline_query);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-04 17:44:05 +00:00
|
|
|
|
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', 'Основной бот запущен, опрос обновлений...');
|
|
|
|
|
|
|
2025-12-04 18:02:36 +00:00
|
|
|
|
// При первом запуске получить все обновления и установить offset на последний,
|
|
|
|
|
|
// чтобы не отвечать на старые команды
|
|
|
|
|
|
const initializeOffset = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await axios.get(`${TELEGRAM_API}/getUpdates`, {
|
|
|
|
|
|
params: {
|
|
|
|
|
|
timeout: 1,
|
2025-12-07 22:15:00 +00:00
|
|
|
|
allowed_updates: ['message', 'inline_query']
|
2025-12-04 18:02:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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 });
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-04 17:44:05 +00:00
|
|
|
|
const poll = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await axios.get(`${TELEGRAM_API}/getUpdates`, {
|
|
|
|
|
|
params: {
|
|
|
|
|
|
offset,
|
|
|
|
|
|
timeout: 30,
|
2025-12-07 22:15:00 +00:00
|
|
|
|
allowed_updates: ['message', 'inline_query']
|
2025-12-04 17:44:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const updates = response.data.result || [];
|
|
|
|
|
|
|
|
|
|
|
|
for (const update of updates) {
|
|
|
|
|
|
offset = update.update_id + 1;
|
|
|
|
|
|
await processUpdate(update);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Продолжить опрос
|
|
|
|
|
|
setTimeout(poll, 1000);
|
|
|
|
|
|
} catch (error) {
|
2025-12-04 21:23:24 +00:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2025-12-04 17:44:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-12-04 18:02:36 +00:00
|
|
|
|
// Сначала инициализировать offset, затем начать опрос
|
|
|
|
|
|
initializeOffset().then(() => {
|
|
|
|
|
|
poll();
|
|
|
|
|
|
});
|
2025-12-04 17:44:05 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const startMainBot = () => {
|
|
|
|
|
|
if (!BOT_TOKEN) {
|
|
|
|
|
|
log('warn', 'Основной бот не запущен: TELEGRAM_BOT_TOKEN не установлен');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pollUpdates();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
startMainBot,
|
|
|
|
|
|
sendMessageToAllUsers,
|
|
|
|
|
|
sendMessage
|
|
|
|
|
|
};
|
|
|
|
|
|
|