From ca7c508e576af404e492b6e06178cd2cc6fa6922 Mon Sep 17 00:00:00 2001 From: glpshchn <464976@niuitmo.ru> Date: Mon, 8 Dec 2025 17:06:57 +0300 Subject: [PATCH] Update files --- backend/bot.js | 32 +++++++++++++++++++++++++++++++- backend/routes/bot.js | 24 +++++++++++++++++++++--- backend/routes/search.js | 27 ++++++++++++++++++--------- frontend/src/utils/api.js | 17 +++++++++++++++-- 4 files changed, 85 insertions(+), 15 deletions(-) diff --git a/backend/bot.js b/backend/bot.js index 5d3e2ac..9e68a42 100644 --- a/backend/bot.js +++ b/backend/bot.js @@ -77,10 +77,23 @@ async function sendPhotoToUser(userId, photoUrl, caption) { throw new Error('TELEGRAM_BOT_TOKEN не установлен'); } + // Валидация параметров + if (!userId || (typeof userId !== 'number' && typeof userId !== 'string')) { + throw new Error('userId должен быть числом или строкой'); + } + + if (!photoUrl || typeof photoUrl !== 'string') { + throw new Error('photoUrl обязателен и должен быть строкой'); + } + try { // Получаем оригинальный URL (если это прокси URL) let finalPhotoUrl = getOriginalUrl(photoUrl); + if (!finalPhotoUrl) { + throw new Error('Не удалось получить URL изображения'); + } + // Если это все еще относительный URL (локальный файл), используем публичный URL if (finalPhotoUrl.startsWith('/')) { const baseUrl = process.env.FRONTEND_URL || process.env.API_URL || 'https://nakama.glpshchn.ru'; @@ -167,7 +180,24 @@ async function sendPhotoToUser(userId, photoUrl, caption) { return response.data; } catch (error) { - console.error('Ошибка отправки медиа:', error.response?.data || error.message); + const errorDetails = { + message: error.message, + userId: userId, + photoUrl: photoUrl, + telegramError: error.response?.data + }; + + console.error('Ошибка отправки медиа:', errorDetails); + + // Если это ошибка от Telegram API, пробрасываем её с более понятным сообщением + if (error.response?.data) { + const telegramError = error.response.data; + const errorMessage = telegramError.description || telegramError.error_code + ? `Telegram API ошибка: ${telegramError.description || `Код ${telegramError.error_code}`}` + : error.message; + throw new Error(errorMessage); + } + throw error; } } diff --git a/backend/routes/bot.js b/backend/routes/bot.js index e2ae2dc..9825cac 100644 --- a/backend/routes/bot.js +++ b/backend/routes/bot.js @@ -12,7 +12,19 @@ router.post('/send-photo', authenticate, async (req, res) => { return res.status(400).json({ error: 'userId и photoUrl обязательны' }); } - const result = await sendPhotoToUser(userId, photoUrl, caption); + // Преобразуем userId в число, если это строка (Telegram API ожидает число) + const telegramUserId = typeof userId === 'string' ? parseInt(userId, 10) : userId; + + if (isNaN(telegramUserId)) { + return res.status(400).json({ error: 'userId должен быть числом' }); + } + + // Валидация URL + if (typeof photoUrl !== 'string' || photoUrl.trim().length === 0) { + return res.status(400).json({ error: 'photoUrl должен быть непустой строкой' }); + } + + const result = await sendPhotoToUser(telegramUserId, photoUrl.trim(), caption); res.json({ success: true, @@ -20,10 +32,16 @@ router.post('/send-photo', authenticate, async (req, res) => { result }); } catch (error) { - console.error('Ошибка отправки:', error); + console.error('Ошибка отправки фото:', { + error: error.message, + stack: error.stack, + response: error.response?.data, + userId: req.body?.userId, + photoUrl: req.body?.photoUrl + }); res.status(500).json({ error: 'Ошибка отправки изображения', - details: error.message + details: error.response?.data?.description || error.message }); } }); diff --git a/backend/routes/search.js b/backend/routes/search.js index 64d3f8f..68e20e6 100644 --- a/backend/routes/search.js +++ b/backend/routes/search.js @@ -7,7 +7,8 @@ const config = require('../config'); // e621 требует описательный User-Agent с контактами const E621_USER_AGENT = 'NakamaApp/1.0 (by glpshchn on e621)'; -const CACHE_TTL_MS = 60 * 1000; // 1 минута +const CACHE_TTL_MS = 60 * 1000; // 1 минута для поиска +const TAGS_CACHE_TTL_MS = 10 * 60 * 1000; // 10 минут для тегов (автокомплит) const searchCache = new Map(); @@ -25,7 +26,7 @@ function getFromCache(key) { return entry.data; } -function setCache(key, data) { +function setCache(key, data, ttl = CACHE_TTL_MS) { if (searchCache.size > 200) { // Удалить устаревшие записи, если превышен лимит for (const [cacheKey, entry] of searchCache.entries()) { @@ -42,7 +43,7 @@ function setCache(key, data) { } searchCache.set(key, { data, - expires: Date.now() + CACHE_TTL_MS + expires: Date.now() + ttl }); } @@ -338,7 +339,13 @@ router.get('/furry/tags', authenticate, async (req, res) => { try { const { query } = req.query; - if (!query || query.length < 2) { + // Быстрый возврат для очень коротких запросов + if (!query || query.length < 1) { + return res.json({ tags: [] }); + } + + // Для запросов длиной 1 символ возвращаем пустой результат без запроса к API + if (query.length < 2) { return res.json({ tags: [] }); } @@ -363,7 +370,7 @@ router.get('/furry/tags', authenticate, async (req, res) => { 'User-Agent': E621_USER_AGENT, 'Authorization': `Basic ${auth}` }, - timeout: 10000, + timeout: 5000, // Уменьшили таймаут до 5 секунд для более быстрого ответа validateStatus: (status) => status < 500 // Не бросать ошибку для 429 }); @@ -388,7 +395,7 @@ router.get('/furry/tags', authenticate, async (req, res) => { headers: { 'User-Agent': E621_USER_AGENT }, - timeout: 10000, + timeout: 5000, // Уменьшили таймаут до 5 секунд validateStatus: (status) => status < 500 }); @@ -432,7 +439,8 @@ router.get('/furry/tags', authenticate, async (req, res) => { })); const payload = { tags }; - setCache(cacheKey, payload); + // Используем более длительное кэширование для тегов (10 минут) + setCache(cacheKey, payload, TAGS_CACHE_TTL_MS); return res.json(payload); } catch (error) { // Обработка 429 ошибок @@ -481,7 +489,7 @@ router.get('/anime/tags', authenticate, async (req, res) => { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, - timeout: 30000, + timeout: 5000, // Уменьшили таймаут до 5 секунд для более быстрого ответа validateStatus: (status) => status < 500 // Не бросать ошибку для 429 }); @@ -513,7 +521,8 @@ router.get('/anime/tags', authenticate, async (req, res) => { })).filter(tag => tag.name); const payload = { tags }; - setCache(cacheKey, payload); + // Используем более длительное кэширование для тегов (10 минут) + setCache(cacheKey, payload, TAGS_CACHE_TTL_MS); return res.json(payload); } catch (error) { // Обработка 429 ошибок diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 5146cf7..757234b 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -295,9 +295,22 @@ export const sendPhotoToTelegram = async (photoUrl) => { throw new Error('Не удалось получить ID пользователя из Telegram') } + // Убеждаемся, что userId - это число (Telegram API ожидает число) + const userId = typeof telegramUser.id === 'string' + ? parseInt(telegramUser.id, 10) + : telegramUser.id + + if (isNaN(userId)) { + throw new Error('Неверный формат ID пользователя') + } + + if (!photoUrl || typeof photoUrl !== 'string') { + throw new Error('URL изображения обязателен') + } + const response = await api.post('/bot/send-photo', { - userId: telegramUser.id, - photoUrl, + userId: userId, + photoUrl: photoUrl.trim(), caption: 'Из Nakama' }) return response.data