From f9aa9dfb29de04fbdc45a10a7afeb6a14c20a2bd Mon Sep 17 00:00:00 2001 From: gitadmin Date: Sat, 10 Jan 2026 17:02:58 +0300 Subject: [PATCH] =?UTF-8?q?[=D0=92=D0=9E=D0=9B=D0=9A]=20=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=B4=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tg_publish.py | 263 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 170 insertions(+), 93 deletions(-) diff --git a/tg_publish.py b/tg_publish.py index 61454a4..05a88ef 100644 --- a/tg_publish.py +++ b/tg_publish.py @@ -193,6 +193,81 @@ def _send_photo_sync(send_photo_url, image_data, caption, chat_id, disable_notif # Пробрасываем остальные исключения как есть raise +def _edit_caption_sync(edit_caption_url, chat_id, message_id, caption, timeout): + """ + Синхронная функция для редактирования caption через requests. + Вызывается из отдельного потока для избежания проблем с event loop в gunicorn. + + Args: + edit_caption_url: URL для редактирования caption в Telegram API + chat_id: ID чата/канала + message_id: ID сообщения для редактирования + caption: Новый текст caption + timeout: Кортеж (connect_timeout, read_timeout) или число для общего таймаута + + Returns: + requests.Response объект + """ + try: + data = { + 'chat_id': chat_id, + 'message_id': message_id, + 'caption': caption, + 'parse_mode': 'HTML' + } + + response = requests.post( + edit_caption_url, + json=data, + timeout=timeout + ) + + return response + except requests.exceptions.Timeout as e: + raise + except requests.exceptions.RequestException as e: + raise + except Exception as e: + raise + +def _edit_text_sync(edit_text_url, chat_id, message_id, text, timeout): + """ + Синхронная функция для редактирования текста через requests. + Вызывается из отдельного потока для избежания проблем с event loop в gunicorn. + + Args: + edit_text_url: URL для редактирования текста в Telegram API + chat_id: ID чата/канала + message_id: ID сообщения для редактирования + text: Новый текст сообщения + timeout: Кортеж (connect_timeout, read_timeout) или число для общего таймаута + + Returns: + requests.Response объект + """ + try: + data = { + 'chat_id': chat_id, + 'message_id': message_id, + 'text': text, + 'parse_mode': 'HTML', + 'disable_web_page_preview': True + } + + response = requests.post( + edit_text_url, + json=data, + timeout=timeout + ) + + return response + except requests.exceptions.Timeout as e: + raise + except requests.exceptions.RequestException as e: + raise + except Exception as e: + raise + async def publish_to_tg(vk_post_id): """Публикация одной записи по VK post ID""" logger.info(f"Запуск публикации для записи VK ID {vk_post_id}") @@ -552,32 +627,41 @@ async def publish_to_tg(vk_post_id): # Используем message_has_image для определения типа сообщения # Если было отправлено изображение, редактируем caption, иначе - текст if message_has_image: - # Сообщение с изображением - редактируем caption + # Сообщение с изображением - редактируем caption через requests + edit_caption_success = False try: - edit_caption_payload = { - 'chat_id': CHANNEL_ID, - 'message_id': message_id, - 'caption': final_text, - 'parse_mode': 'HTML' - } - try: - edit_caption_response = await httpx_client.post(edit_caption_url, json=edit_caption_payload) - except httpx.TimeoutException as e: - logger.error(f"ТАЙМАУТ при редактировании подписи для записи VK ID {vk_post_id}") - logger.error(f"Детали таймаута: {type(e).__name__}: {e}") - if hasattr(e, 'request'): - logger.error(f"Запрос, вызвавший таймаут: {e.request.method} {e.request.url if hasattr(e.request, 'url') else 'N/A'}") - raise # Пробрасываем таймаут наверх + # Используем requests через asyncio.to_thread() для стабильной работы в gunicorn + edit_timeout = (10.0, 30.0) # (connect, read) + + edit_caption_response = await asyncio.to_thread( + _edit_caption_sync, + edit_caption_url, + CHANNEL_ID, + message_id, + final_text, + edit_timeout + ) if edit_caption_response.status_code == 200: - edit_result = edit_caption_response.json() - if not edit_result.get('ok'): + try: + edit_result = edit_caption_response.json() + except Exception as json_error: + logger.error(f"Ошибка при парсинге JSON ответа при редактировании caption для записи VK ID {vk_post_id}: {json_error}") + logger.error(f"Текст ответа: {edit_caption_response.text[:500]}") + raise Exception(f"Ошибка парсинга JSON ответа: {json_error}") + + if edit_result.get('ok'): + edit_caption_success = True + logger.info(f"Caption успешно отредактирован для записи VK ID {vk_post_id}") + else: + # Ошибка в ответе API log_telegram_error(edit_caption_response, f"[Редактирование подписи для VK ID {vk_post_id}]") error_code = edit_result.get('error_code') if error_code == 429: retry_after = edit_result.get('parameters', {}).get('retry_after', 60) raise RetryAfterException(retry_after) - raise Exception(f"API error: {edit_result.get('description', 'Unknown error')}") + # Не критичная ошибка - сообщение уже опубликовано + logger.warning(f"Не удалось отредактировать caption для записи VK ID {vk_post_id}: {edit_result.get('description', 'Unknown error')}. Сообщение опубликовано, но без ссылок.") else: log_telegram_error(edit_caption_response, f"[Редактирование подписи для VK ID {vk_post_id}]") if edit_caption_response.status_code == 429: @@ -588,50 +672,64 @@ async def publish_to_tg(vk_post_id): except: pass raise RetryAfterException(retry_after) - raise Exception(f"HTTP {edit_caption_response.status_code}: {edit_caption_response.text}") + # Не критичная ошибка - сообщение уже опубликовано + logger.warning(f"Не удалось отредактировать caption для записи VK ID {vk_post_id}: HTTP {edit_caption_response.status_code}. Сообщение опубликовано, но без ссылок.") except RetryAfterException: raise - except httpx.TimeoutException as e: - logger.error(f"ТАЙМАУТ при редактировании подписи для записи VK ID {vk_post_id}") - logger.error(f"Тип исключения: {type(e).__name__}") - logger.error(f"Сообщение об ошибке: {str(e)}") - logger.error(f"Полная информация об исключении: {repr(e)}") - if hasattr(e, 'request'): - logger.error(f"Запрос, вызвавший таймаут: {e.request.method} {e.request.url if hasattr(e.request, 'url') else 'N/A'}") - logger.error(f"Трассировка стека:\n{traceback.format_exc()}") - raise # Пробрасываем таймаут наверх except Exception as e: - logger.warning(f"Не удалось отредактировать подпись для записи VK ID {vk_post_id}: {e}. Пробуем отредактировать текстовое сообщение.") - edit_text_payload = { - 'chat_id': CHANNEL_ID, - 'message_id': message_id, - 'text': final_text, - 'parse_mode': 'HTML', - 'disable_web_page_preview': True - } - try: - edit_text_response = await httpx_client.post(edit_text_url, json=edit_text_payload) - except httpx.TimeoutException as timeout_e: - logger.error(f"ТАЙМАУТ при редактировании текста (fallback) для записи VK ID {vk_post_id}") - logger.error(f"Тип исключения: {type(timeout_e).__name__}") - logger.error(f"Сообщение об ошибке: {str(timeout_e)}") - logger.error(f"Полная информация об исключении: {repr(timeout_e)}") - if hasattr(timeout_e, 'request'): - logger.error(f"Запрос, вызвавший таймаут: {timeout_e.request.method} {timeout_e.request.url if hasattr(timeout_e.request, 'url') else 'N/A'}") - logger.error(f"Трассировка стека:\n{traceback.format_exc()}") - raise # Пробрасываем таймаут наверх + # Проверяем, является ли это таймаутом + is_timeout = ( + isinstance(e, requests.exceptions.Timeout) or + 'Timeout' in str(e) or + 'timeout' in str(e).lower() or + 'disconnected' in str(e).lower() + ) + + if is_timeout: + logger.warning(f"Таймаут при редактировании caption для записи VK ID {vk_post_id}: {e}. Сообщение опубликовано, но без ссылок.") + else: + logger.warning(f"Не удалось отредактировать caption для записи VK ID {vk_post_id}: {e}. Сообщение опубликовано, но без ссылок.") + + # Не пробрасываем исключение - сообщение уже опубликовано, просто без ссылок + # Это считается частичным успехом + else: + # Текстовое сообщение - редактируем текст через requests + edit_text_success = False + try: + # Используем requests через asyncio.to_thread() для стабильной работы в gunicorn + edit_timeout = (10.0, 30.0) # (connect, read) + + edit_text_response = await asyncio.to_thread( + _edit_text_sync, + edit_text_url, + CHANNEL_ID, + message_id, + final_text, + edit_timeout + ) if edit_text_response.status_code == 200: - edit_result = edit_text_response.json() - if not edit_result.get('ok'): - log_telegram_error(edit_text_response, f"[Редактирование текста (fallback) для VK ID {vk_post_id}]") + try: + edit_result = edit_text_response.json() + except Exception as json_error: + logger.error(f"Ошибка при парсинге JSON ответа при редактировании текста для записи VK ID {vk_post_id}: {json_error}") + logger.error(f"Текст ответа: {edit_text_response.text[:500]}") + raise Exception(f"Ошибка парсинга JSON ответа: {json_error}") + + if edit_result.get('ok'): + edit_text_success = True + logger.info(f"Текст успешно отредактирован для записи VK ID {vk_post_id}") + else: + # Ошибка в ответе API + log_telegram_error(edit_text_response, f"[Редактирование текста для VK ID {vk_post_id}]") error_code = edit_result.get('error_code') if error_code == 429: retry_after = edit_result.get('parameters', {}).get('retry_after', 60) raise RetryAfterException(retry_after) - raise Exception(f"API error: {edit_result.get('description', 'Unknown error')}") + # Не критичная ошибка - сообщение уже опубликовано + logger.warning(f"Не удалось отредактировать текст для записи VK ID {vk_post_id}: {edit_result.get('description', 'Unknown error')}. Сообщение опубликовано, но без ссылок.") else: - log_telegram_error(edit_text_response, f"[Редактирование текста (fallback) для VK ID {vk_post_id}]") + log_telegram_error(edit_text_response, f"[Редактирование текста для VK ID {vk_post_id}]") if edit_text_response.status_code == 429: retry_after = 60 try: @@ -640,47 +738,26 @@ async def publish_to_tg(vk_post_id): except: pass raise RetryAfterException(retry_after) - raise Exception(f"HTTP {edit_text_response.status_code}: {edit_text_response.text}") - else: - edit_text_payload = { - 'chat_id': CHANNEL_ID, - 'message_id': message_id, - 'text': final_text, - 'parse_mode': 'HTML', - 'disable_web_page_preview': True - } - try: - edit_text_response = await httpx_client.post(edit_text_url, json=edit_text_payload) - except httpx.TimeoutException as e: - logger.error(f"ТАЙМАУТ при редактировании текста для записи VK ID {vk_post_id}") - logger.error(f"Тип исключения: {type(e).__name__}") - logger.error(f"Сообщение об ошибке: {str(e)}") - logger.error(f"Полная информация об исключении: {repr(e)}") - if hasattr(e, 'request'): - logger.error(f"Запрос, вызвавший таймаут: {e.request.method} {e.request.url if hasattr(e.request, 'url') else 'N/A'}") - logger.error(f"Трассировка стека:\n{traceback.format_exc()}") - raise # Пробрасываем таймаут наверх - - if edit_text_response.status_code == 200: - edit_result = edit_text_response.json() - if not edit_result.get('ok'): - log_telegram_error(edit_text_response, f"[Редактирование текста для VK ID {vk_post_id}]") - error_code = edit_result.get('error_code') - if error_code == 429: - retry_after = edit_result.get('parameters', {}).get('retry_after', 60) - raise RetryAfterException(retry_after) - raise Exception(f"API error: {edit_result.get('description', 'Unknown error')}") - else: - log_telegram_error(edit_text_response, f"[Редактирование текста для VK ID {vk_post_id}]") - if edit_text_response.status_code == 429: - retry_after = 60 - try: - error_data = edit_text_response.json() - retry_after = error_data.get('parameters', {}).get('retry_after', 60) - except: - pass - raise RetryAfterException(retry_after) - raise Exception(f"HTTP {edit_text_response.status_code}: {edit_text_response.text}") + # Не критичная ошибка - сообщение уже опубликовано + logger.warning(f"Не удалось отредактировать текст для записи VK ID {vk_post_id}: HTTP {edit_text_response.status_code}. Сообщение опубликовано, но без ссылок.") + except RetryAfterException: + raise + except Exception as e: + # Проверяем, является ли это таймаутом + is_timeout = ( + isinstance(e, requests.exceptions.Timeout) or + 'Timeout' in str(e) or + 'timeout' in str(e).lower() or + 'disconnected' in str(e).lower() + ) + + if is_timeout: + logger.warning(f"Таймаут при редактировании текста для записи VK ID {vk_post_id}: {e}. Сообщение опубликовано, но без ссылок.") + else: + logger.warning(f"Не удалось отредактировать текст для записи VK ID {vk_post_id}: {e}. Сообщение опубликовано, но без ссылок.") + + # Не пробрасываем исключение - сообщение уже опубликовано, просто без ссылок + # Это считается частичным успехом await asyncio.sleep(2) # задержка между обращениями к телеграм