This commit is contained in:
+170
-93
@@ -193,6 +193,81 @@ def _send_photo_sync(send_photo_url, image_data, caption, chat_id, disable_notif
|
|||||||
# Пробрасываем остальные исключения как есть
|
# Пробрасываем остальные исключения как есть
|
||||||
raise
|
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):
|
async def publish_to_tg(vk_post_id):
|
||||||
"""Публикация одной записи по VK post ID"""
|
"""Публикация одной записи по VK post ID"""
|
||||||
logger.info(f"Запуск публикации для записи VK 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 для определения типа сообщения
|
# Используем message_has_image для определения типа сообщения
|
||||||
# Если было отправлено изображение, редактируем caption, иначе - текст
|
# Если было отправлено изображение, редактируем caption, иначе - текст
|
||||||
if message_has_image:
|
if message_has_image:
|
||||||
# Сообщение с изображением - редактируем caption
|
# Сообщение с изображением - редактируем caption через requests
|
||||||
|
edit_caption_success = False
|
||||||
try:
|
try:
|
||||||
edit_caption_payload = {
|
# Используем requests через asyncio.to_thread() для стабильной работы в gunicorn
|
||||||
'chat_id': CHANNEL_ID,
|
edit_timeout = (10.0, 30.0) # (connect, read)
|
||||||
'message_id': message_id,
|
|
||||||
'caption': final_text,
|
edit_caption_response = await asyncio.to_thread(
|
||||||
'parse_mode': 'HTML'
|
_edit_caption_sync,
|
||||||
}
|
edit_caption_url,
|
||||||
try:
|
CHANNEL_ID,
|
||||||
edit_caption_response = await httpx_client.post(edit_caption_url, json=edit_caption_payload)
|
message_id,
|
||||||
except httpx.TimeoutException as e:
|
final_text,
|
||||||
logger.error(f"ТАЙМАУТ при редактировании подписи для записи VK ID {vk_post_id}")
|
edit_timeout
|
||||||
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 # Пробрасываем таймаут наверх
|
|
||||||
|
|
||||||
if edit_caption_response.status_code == 200:
|
if edit_caption_response.status_code == 200:
|
||||||
edit_result = edit_caption_response.json()
|
try:
|
||||||
if not edit_result.get('ok'):
|
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}]")
|
log_telegram_error(edit_caption_response, f"[Редактирование подписи для VK ID {vk_post_id}]")
|
||||||
error_code = edit_result.get('error_code')
|
error_code = edit_result.get('error_code')
|
||||||
if error_code == 429:
|
if error_code == 429:
|
||||||
retry_after = edit_result.get('parameters', {}).get('retry_after', 60)
|
retry_after = edit_result.get('parameters', {}).get('retry_after', 60)
|
||||||
raise RetryAfterException(retry_after)
|
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:
|
else:
|
||||||
log_telegram_error(edit_caption_response, f"[Редактирование подписи для VK ID {vk_post_id}]")
|
log_telegram_error(edit_caption_response, f"[Редактирование подписи для VK ID {vk_post_id}]")
|
||||||
if edit_caption_response.status_code == 429:
|
if edit_caption_response.status_code == 429:
|
||||||
@@ -588,50 +672,64 @@ async def publish_to_tg(vk_post_id):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
raise RetryAfterException(retry_after)
|
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:
|
except RetryAfterException:
|
||||||
raise
|
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:
|
except Exception as e:
|
||||||
logger.warning(f"Не удалось отредактировать подпись для записи VK ID {vk_post_id}: {e}. Пробуем отредактировать текстовое сообщение.")
|
# Проверяем, является ли это таймаутом
|
||||||
edit_text_payload = {
|
is_timeout = (
|
||||||
'chat_id': CHANNEL_ID,
|
isinstance(e, requests.exceptions.Timeout) or
|
||||||
'message_id': message_id,
|
'Timeout' in str(e) or
|
||||||
'text': final_text,
|
'timeout' in str(e).lower() or
|
||||||
'parse_mode': 'HTML',
|
'disconnected' in str(e).lower()
|
||||||
'disable_web_page_preview': True
|
)
|
||||||
}
|
|
||||||
try:
|
if is_timeout:
|
||||||
edit_text_response = await httpx_client.post(edit_text_url, json=edit_text_payload)
|
logger.warning(f"Таймаут при редактировании caption для записи VK ID {vk_post_id}: {e}. Сообщение опубликовано, но без ссылок.")
|
||||||
except httpx.TimeoutException as timeout_e:
|
else:
|
||||||
logger.error(f"ТАЙМАУТ при редактировании текста (fallback) для записи VK ID {vk_post_id}")
|
logger.warning(f"Не удалось отредактировать caption для записи VK ID {vk_post_id}: {e}. Сообщение опубликовано, но без ссылок.")
|
||||||
logger.error(f"Тип исключения: {type(timeout_e).__name__}")
|
|
||||||
logger.error(f"Сообщение об ошибке: {str(timeout_e)}")
|
# Не пробрасываем исключение - сообщение уже опубликовано, просто без ссылок
|
||||||
logger.error(f"Полная информация об исключении: {repr(timeout_e)}")
|
# Это считается частичным успехом
|
||||||
if hasattr(timeout_e, 'request'):
|
else:
|
||||||
logger.error(f"Запрос, вызвавший таймаут: {timeout_e.request.method} {timeout_e.request.url if hasattr(timeout_e.request, 'url') else 'N/A'}")
|
# Текстовое сообщение - редактируем текст через requests
|
||||||
logger.error(f"Трассировка стека:\n{traceback.format_exc()}")
|
edit_text_success = False
|
||||||
raise # Пробрасываем таймаут наверх
|
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:
|
if edit_text_response.status_code == 200:
|
||||||
edit_result = edit_text_response.json()
|
try:
|
||||||
if not edit_result.get('ok'):
|
edit_result = edit_text_response.json()
|
||||||
log_telegram_error(edit_text_response, f"[Редактирование текста (fallback) для VK ID {vk_post_id}]")
|
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')
|
error_code = edit_result.get('error_code')
|
||||||
if error_code == 429:
|
if error_code == 429:
|
||||||
retry_after = edit_result.get('parameters', {}).get('retry_after', 60)
|
retry_after = edit_result.get('parameters', {}).get('retry_after', 60)
|
||||||
raise RetryAfterException(retry_after)
|
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:
|
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:
|
if edit_text_response.status_code == 429:
|
||||||
retry_after = 60
|
retry_after = 60
|
||||||
try:
|
try:
|
||||||
@@ -640,47 +738,26 @@ async def publish_to_tg(vk_post_id):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
raise RetryAfterException(retry_after)
|
raise RetryAfterException(retry_after)
|
||||||
raise Exception(f"HTTP {edit_text_response.status_code}: {edit_text_response.text}")
|
# Не критичная ошибка - сообщение уже опубликовано
|
||||||
else:
|
logger.warning(f"Не удалось отредактировать текст для записи VK ID {vk_post_id}: HTTP {edit_text_response.status_code}. Сообщение опубликовано, но без ссылок.")
|
||||||
edit_text_payload = {
|
except RetryAfterException:
|
||||||
'chat_id': CHANNEL_ID,
|
raise
|
||||||
'message_id': message_id,
|
except Exception as e:
|
||||||
'text': final_text,
|
# Проверяем, является ли это таймаутом
|
||||||
'parse_mode': 'HTML',
|
is_timeout = (
|
||||||
'disable_web_page_preview': True
|
isinstance(e, requests.exceptions.Timeout) or
|
||||||
}
|
'Timeout' in str(e) or
|
||||||
try:
|
'timeout' in str(e).lower() or
|
||||||
edit_text_response = await httpx_client.post(edit_text_url, json=edit_text_payload)
|
'disconnected' in str(e).lower()
|
||||||
except httpx.TimeoutException as e:
|
)
|
||||||
logger.error(f"ТАЙМАУТ при редактировании текста для записи VK ID {vk_post_id}")
|
|
||||||
logger.error(f"Тип исключения: {type(e).__name__}")
|
if is_timeout:
|
||||||
logger.error(f"Сообщение об ошибке: {str(e)}")
|
logger.warning(f"Таймаут при редактировании текста для записи VK ID {vk_post_id}: {e}. Сообщение опубликовано, но без ссылок.")
|
||||||
logger.error(f"Полная информация об исключении: {repr(e)}")
|
else:
|
||||||
if hasattr(e, 'request'):
|
logger.warning(f"Не удалось отредактировать текст для записи VK ID {vk_post_id}: {e}. Сообщение опубликовано, но без ссылок.")
|
||||||
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}")
|
|
||||||
|
|
||||||
await asyncio.sleep(2) # задержка между обращениями к телеграм
|
await asyncio.sleep(2) # задержка между обращениями к телеграм
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user