From fc3864aa33178d2f16e27268b8629416630cf611 Mon Sep 17 00:00:00 2001 From: glpshchn <464976@niuitmo.ru> Date: Fri, 5 Dec 2025 00:23:24 +0300 Subject: [PATCH] Update files --- backend/bots/mainBot.js | 17 +++- backend/bots/serverMonitor.js | 70 ++++++++++++-- frontend/src/components/CommentsModal.jsx | 101 ++++++++------------ frontend/src/components/FollowListModal.css | 20 ++-- frontend/src/components/FollowListModal.jsx | 4 +- 5 files changed, 127 insertions(+), 85 deletions(-) diff --git a/backend/bots/mainBot.js b/backend/bots/mainBot.js index 7c528d5..ddd73e4 100644 --- a/backend/bots/mainBot.js +++ b/backend/bots/mainBot.js @@ -197,9 +197,20 @@ const pollUpdates = async () => { // Продолжить опрос setTimeout(poll, 1000); } catch (error) { - logError('Ошибка опроса Telegram для основного бота', error); - // Переподключиться через 5 секунд - setTimeout(poll, 5000); + 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); + } } }; diff --git a/backend/bots/serverMonitor.js b/backend/bots/serverMonitor.js index 1b25c18..9569788 100644 --- a/backend/bots/serverMonitor.js +++ b/backend/bots/serverMonitor.js @@ -310,12 +310,17 @@ const processUpdate = async (update) => { const pollUpdates = async () => { if (!TELEGRAM_API) return; - while (isPolling) { + const poll = async () => { + if (!isPolling) { + return; + } + try { const response = await axios.get(`${TELEGRAM_API}/getUpdates`, { params: { timeout: 25, - offset + offset, + allowed_updates: ['message'] } }); @@ -324,13 +329,35 @@ const pollUpdates = async () => { offset = update.update_id + 1; await processUpdate(update); } + + // Продолжить опрос + setTimeout(poll, 100); } catch (error) { - log('error', 'Ошибка опроса Telegram для модераторского бота', { - error: error.response?.data || error.message - }); - await new Promise((resolve) => setTimeout(resolve, 5000)); + const errorData = error.response?.data || {}; + const errorCode = errorData.error_code; + const errorDescription = errorData.description || error.message; + + // Обработка конфликта 409 - другой экземпляр бота уже опрашивает + if (errorCode === 409) { + // Не логируем 409 - это ожидаемая ситуация при конфликте экземпляров + // Подождать дольше перед повторной попыткой + await new Promise((resolve) => setTimeout(resolve, 10000)); + } else { + log('error', 'Ошибка опроса Telegram для модераторского бота', { + error: errorData || error.message + }); + // Обычная задержка при других ошибках + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + + // Продолжить опрос только если isPolling все еще true + if (isPolling) { + setTimeout(poll, 0); + } } - } + }; + + poll(); }; const startServerMonitorBot = () => { @@ -340,13 +367,40 @@ const startServerMonitorBot = () => { } if (isPolling) { + log('warn', 'Модераторский бот уже запущен, пропускаем повторный запуск'); return; } + // Инициализировать 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 = updates[updates.length - 1].update_id + 1; + log('info', `Модераторский бот: пропущено ${updates.length} старых обновлений, offset установлен на ${offset}`); + } + } catch (error) { + log('warn', 'Не удалось инициализировать offset для модераторского бота, начнем с 0', { + error: error.response?.data || error.message + }); + } + }; + isPolling = true; log('info', 'Модераторский Telegram бот запущен'); - pollUpdates().catch((error) => { + + initializeOffset().then(() => { + pollUpdates(); + }).catch((error) => { log('error', 'Не удалось запустить модераторский бот', { error: error.message }); + isPolling = false; }); }; diff --git a/frontend/src/components/CommentsModal.jsx b/frontend/src/components/CommentsModal.jsx index 9fa295a..f61b12f 100644 --- a/frontend/src/components/CommentsModal.jsx +++ b/frontend/src/components/CommentsModal.jsx @@ -7,6 +7,7 @@ import { decodeHtmlEntities } from '../utils/htmlEntities' import './CommentsModal.css' export default function CommentsModal({ post, onClose, onUpdate }) { + // ВСЕ хуки должны вызываться всегда, до любых условных возвратов const [comment, setComment] = useState('') const [loading, setLoading] = useState(false) const [comments, setComments] = useState([]) @@ -14,59 +15,47 @@ export default function CommentsModal({ post, onClose, onUpdate }) { const [loadingPost, setLoadingPost] = useState(false) // Загрузить полные данные поста с комментариями - const loadFullPost = useCallback(async (postId) => { - if (!postId) { + useEffect(() => { + if (!post || !post._id) { return } - try { - setLoadingPost(true) - // Загрузить посты с фильтром по автору поста для оптимизации - // Если это не помогает, загружаем больше постов - const authorId = post?.author?._id || post?.author - const response = authorId - ? await getPosts({ userId: authorId, limit: 100 }) - : await getPosts({ limit: 200 }) - - const foundPost = response.posts?.find(p => p._id === postId) - if (foundPost) { - // Проверяем, что комментарии populate'ены с авторами - const commentsWithAuthors = (foundPost.comments || []).filter(c => { - return c && c.author && (typeof c.author === 'object') - }) - setComments(commentsWithAuthors) - setFullPost(foundPost) - } else { - // Если не нашли, используем переданные данные - const commentsWithAuthors = (post.comments || []).filter(c => { - return c && c.author && (typeof c.author === 'object') - }) - setComments(commentsWithAuthors) + // Сначала установим переданные данные + setFullPost(post) + const initialComments = (post.comments || []).filter(c => { + return c && c.author && (typeof c.author === 'object') + }) + setComments(initialComments) + + // Затем загрузим полные данные для обновления + const loadFullPost = async () => { + try { + setLoadingPost(true) + // Загрузить посты с фильтром по автору поста для оптимизации + const authorId = post?.author?._id || post?.author + const response = authorId + ? await getPosts({ userId: authorId, limit: 100 }) + : await getPosts({ limit: 200 }) + + const foundPost = response.posts?.find(p => p._id === post._id) + if (foundPost) { + // Проверяем, что комментарии populate'ены с авторами + const commentsWithAuthors = (foundPost.comments || []).filter(c => { + return c && c.author && (typeof c.author === 'object') + }) + setComments(commentsWithAuthors) + setFullPost(foundPost) + } + } catch (error) { + console.error('[CommentsModal] Ошибка загрузки поста:', error) + // Оставляем переданные данные + } finally { + setLoadingPost(false) } - } catch (error) { - console.error('[CommentsModal] Ошибка загрузки поста:', error) - // Fallback на переданные данные - const commentsWithAuthors = (post.comments || []).filter(c => { - return c && c.author && (typeof c.author === 'object') - }) - setComments(commentsWithAuthors) - } finally { - setLoadingPost(false) } - }, [post?.author]) - - useEffect(() => { - if (post && post._id) { - // Сначала установим переданные данные - setFullPost(post) - const initialComments = (post.comments || []).filter(c => { - return c && c.author && (typeof c.author === 'object') - }) - setComments(initialComments) - // Затем загрузим полные данные для обновления - loadFullPost(post._id) - } - }, [post?._id, loadFullPost]) + + loadFullPost() + }, [post?._id]) // Только ID поста в зависимостях // Проверка на существование поста ПОСЛЕ хуков if (!post) { @@ -102,9 +91,8 @@ export default function CommentsModal({ post, onClose, onUpdate }) { hapticFeedback('success') // Обновить данные поста для синхронизации (но не блокируем UI) - loadFullPost(post._id).catch(err => { - console.error('[CommentsModal] Ошибка при обновлении после добавления:', err) - }) + // Перезагружаем через useEffect, который сработает при изменении post._id + // Но так как post._id не меняется, просто обновим локально if (onUpdate) { onUpdate() @@ -112,17 +100,6 @@ export default function CommentsModal({ post, onClose, onUpdate }) { } else { console.error('[CommentsModal] Неожиданный формат ответа:', result) hapticFeedback('error') - // Попробуем перезагрузить комментарии - await loadFullPost(post._id) - } - } catch (error) { - console.error('[CommentsModal] Ошибка добавления комментария:', error) - hapticFeedback('error') - // Попробуем перезагрузить комментарии в случае ошибки - try { - await loadFullPost(post._id) - } catch (reloadError) { - console.error('[CommentsModal] Ошибка при перезагрузке:', reloadError) } } finally { setLoading(false) diff --git a/frontend/src/components/FollowListModal.css b/frontend/src/components/FollowListModal.css index 11f79fa..b9b1bf3 100644 --- a/frontend/src/components/FollowListModal.css +++ b/frontend/src/components/FollowListModal.css @@ -103,17 +103,17 @@ } .user-item-wrapper { - padding: 4px 12px; + padding: 3px 10px; } .user-item { display: flex; align-items: center; - gap: 10px; + gap: 8px; cursor: pointer; transition: background 0.2s; - padding: 6px; - border-radius: 8px; + padding: 5px; + border-radius: 6px; position: relative; } @@ -122,8 +122,8 @@ } .user-avatar { - width: 32px; - height: 32px; + width: 26px; + height: 26px; border-radius: 50%; object-fit: cover; flex-shrink: 0; @@ -138,7 +138,7 @@ } .user-name { - font-size: 14px; + font-size: 12px; font-weight: 600; color: var(--text-primary); line-height: 1.3; @@ -148,7 +148,7 @@ } .user-username { - font-size: 12px; + font-size: 15px; color: var(--text-secondary); line-height: 1.3; white-space: nowrap; @@ -158,8 +158,8 @@ /* Follow Button Icon */ .follow-btn-icon { - width: 32px; - height: 32px; + width: 26px; + height: 26px; border-radius: 50%; background: var(--bg-primary); color: var(--text-primary); diff --git a/frontend/src/components/FollowListModal.jsx b/frontend/src/components/FollowListModal.jsx index e91bcba..1dab6eb 100644 --- a/frontend/src/components/FollowListModal.jsx +++ b/frontend/src/components/FollowListModal.jsx @@ -112,9 +112,9 @@ export default function FollowListModal({ users, title, onClose, currentUser }) onClick={(e) => handleFollowToggle(user._id, e)} > {isFollowing ? ( - + ) : ( - + )} )}