diff --git a/tg_publish.py b/tg_publish.py index 491917d..465919f 100644 --- a/tg_publish.py +++ b/tg_publish.py @@ -8,6 +8,7 @@ import json import re import httpx import traceback +import io from datetime import datetime, timezone from dotenv import load_dotenv from formatter import get_event_text @@ -105,6 +106,24 @@ def log_telegram_error(response, context=""): except Exception as e: logger.error(f"{context} Ошибка при логировании информации об ошибке: {e}") +async def download_image(image_url, httpx_client): + """Скачивает изображение по URL и возвращает его содержимое в виде bytes""" + try: + logger.info(f"Скачивание изображения: {image_url}") + download_start_time = time.time() + + response = await httpx_client.get(image_url, timeout=httpx.Timeout(connect=10.0, read=60.0, write=30.0, pool=10.0)) + response.raise_for_status() + + download_duration = time.time() - download_start_time + image_data = response.content + logger.info(f"Изображение скачано за {download_duration:.2f} секунд, размер: {len(image_data)} байт") + + return image_data + except Exception as e: + logger.error(f"Ошибка при скачивании изображения {image_url}: {e}") + raise + async def publish_to_tg(vk_post_id): """Публикация одной записи по VK post ID""" logger.info(f"Запуск публикации для записи VK ID {vk_post_id}") @@ -151,6 +170,8 @@ async def publish_to_tg(vk_post_id): # Переменная для отслеживания успешности публикации message_id = None publication_successful = False + # Флаг, указывающий, было ли отправлено изображение (True) или текстовое сообщение (False) + message_has_image = False try: # Проверяем наличие организационного хештега @@ -212,44 +233,40 @@ async def publish_to_tg(vk_post_id): logger.info(f"Длина URL изображения: {len(image_url)} символов") logger.info(f"Длина подписи: {len(text)} символов") - photo_payload = { - 'chat_id': CHANNEL_ID, - 'photo': image_url, - 'caption': text, - 'parse_mode': 'HTML', - 'disable_notification': PUBLISH_SILENTLY - } + # Скачиваем изображение + try: + image_data = await download_image(image_url, httpx_client) + except Exception as download_error: + logger.error(f"Ошибка при скачивании изображения для записи VK ID {vk_post_id}: {download_error}") + raise # Пробрасываем ошибку, чтобы перейти к fallback # Логируем время начала запроса request_start_time = time.time() logger.info(f"Начало запроса отправки фото в {datetime.now(timezone.utc).isoformat()}") - try: - photo_response = await httpx_client.post(send_photo_url, json=photo_payload) - request_duration = time.time() - request_start_time - logger.info(f"Запрос отправки фото завершен за {request_duration:.2f} секунд") - except httpx.TimeoutException as e: - request_duration = time.time() - request_start_time - logger.error(f"ТАЙМАУТ при отправке фото для записи VK ID {vk_post_id}") - logger.error(f"Время до таймаута: {request_duration:.2f} секунд") - logger.error(f"Тип исключения: {type(e).__name__}") - logger.error(f"Сообщение об ошибке: {str(e)}") - logger.error(f"Полная информация об исключении: {repr(e)}") - logger.error(f"URL изображения: {image_url}") - logger.error(f"Длина URL изображения: {len(image_url)} символов") - if hasattr(e, 'request'): - logger.error(f"Запрос, вызвавший таймаут: {e.request.method} {e.request.url if hasattr(e.request, 'url') else 'N/A'}") - # Пытаемся получить информацию о таймауте из исключения - if hasattr(e, 'timeout'): - logger.error(f"Настройки таймаута: {e.timeout}") - logger.error(f"Трассировка стека:\n{traceback.format_exc()}") - raise # Пробрасываем таймаут наверх + # Отправляем изображение как файл через multipart/form-data + # Создаем BytesIO объект для файла + image_file = io.BytesIO(image_data) + files = { + 'photo': ('image.jpg', image_file, 'image/jpeg') + } + data = { + 'chat_id': CHANNEL_ID, + 'caption': text, + 'parse_mode': 'HTML', + 'disable_notification': str(PUBLISH_SILENTLY).lower() + } + + photo_response = await httpx_client.post(send_photo_url, files=files, data=data) + request_duration = time.time() - request_start_time + logger.info(f"Запрос отправки фото завершен за {request_duration:.2f} секунд") if photo_response.status_code == 200: photo_result = photo_response.json() if photo_result.get('ok'): message_data = photo_result.get('result', {}) message_id = message_data.get('message_id') + message_has_image = True # Успешно отправили изображение else: # Ошибка в ответе API - логируем полную информацию log_telegram_error(photo_response, f"[Отправка фото для VK ID {vk_post_id}]") @@ -282,6 +299,7 @@ async def publish_to_tg(vk_post_id): raise # Пробрасываем таймаут наверх except Exception as e: logger.warning(f"Не удалось отправить изображение для записи VK ID {vk_post_id}: {e}. Отправляем текстовое сообщение.") + message_has_image = False # Отправляем текстовое сообщение вместо изображения text_payload = { 'chat_id': CHANNEL_ID, 'text': text, @@ -326,6 +344,7 @@ async def publish_to_tg(vk_post_id): raise Exception(f"HTTP {text_response.status_code}: {text_response.text}") else: # Если это организационное сообщение или нет изображения, отправляем текстовое сообщение + message_has_image = False # Отправляем текстовое сообщение text_payload = { 'chat_id': CHANNEL_ID, 'text': text, @@ -388,7 +407,10 @@ async def publish_to_tg(vk_post_id): edit_caption_url = f"https://api.telegram.org/bot{BOT_TOKEN}/editMessageCaption" edit_text_url = f"https://api.telegram.org/bot{BOT_TOKEN}/editMessageText" - if has_image and not is_org_message: + # Используем message_has_image для определения типа сообщения + # Если было отправлено изображение, редактируем caption, иначе - текст + if message_has_image: + # Сообщение с изображением - редактируем caption try: edit_caption_payload = { 'chat_id': CHANNEL_ID,