[ZILANT] отладка до-редактирования ссылок в пост
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -20,4 +20,4 @@ alwaysApply: true
|
|||||||
|
|
||||||
5. Не поднимать версию за косметические правки без изменения поведения.
|
5. Не поднимать версию за косметические правки без изменения поведения.
|
||||||
|
|
||||||
Текущая базовая версия: **1.1.1**.
|
Текущая базовая версия: **1.1.2**.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Zilant / VOLK — Telegram poster и подписки
|
# Zilant / VOLK — Telegram poster и подписки
|
||||||
|
|
||||||
**Версия:** 1.1.1 ([`VERSION`](VERSION))
|
**Версия:** 1.1.2 ([`VERSION`](VERSION))
|
||||||
|
|
||||||
Автоматическая публикация постов и событий в Telegram-канал, веб-редактор, бот подписок с сезонными deep link.
|
Автоматическая публикация постов и событий в Telegram-канал, веб-редактор, бот подписок с сезонными deep link.
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/).
|
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/).
|
||||||
Версия проекта — в файле [`VERSION`](../VERSION) (SemVer).
|
Версия проекта — в файле [`VERSION`](../VERSION) (SemVer).
|
||||||
|
|
||||||
|
## [1.1.2] - 2026-09-02
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Проверка успешного edit ссылок: Telegram отдаёт plain text и `text_link` entities, а не `href=` в caption; `message is not modified` трактуется как успех (ссылки уже применены). Устраняет ложные ошибки при успешной публикации.
|
||||||
|
|
||||||
## [1.1.1] - 2026-09-02
|
## [1.1.1] - 2026-09-02
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+24
-21
@@ -14,7 +14,7 @@ from telegram_relay import (
|
|||||||
EDIT_LINKS_INITIAL_DELAY_SEC,
|
EDIT_LINKS_INITIAL_DELAY_SEC,
|
||||||
EDIT_LINKS_MAX_ATTEMPTS,
|
EDIT_LINKS_MAX_ATTEMPTS,
|
||||||
EDIT_LINKS_RETRY_INTERVAL_SEC,
|
EDIT_LINKS_RETRY_INTERVAL_SEC,
|
||||||
published_text_has_links,
|
published_message_has_links,
|
||||||
)
|
)
|
||||||
from season_links import subscription_start_link
|
from season_links import subscription_start_link
|
||||||
|
|
||||||
@@ -133,10 +133,11 @@ async def download_image(image_url, httpx_client):
|
|||||||
|
|
||||||
|
|
||||||
async def _edit_event_add_links(httpx_client, has_image, message_id, final_text, entity_label):
|
async def _edit_event_add_links(httpx_client, has_image, message_id, final_text, entity_label):
|
||||||
"""Edit caption/text со ссылками; повтор до появления href= в ответе API."""
|
"""Edit caption/text со ссылками; повтор до подтверждения text_link в ответе API."""
|
||||||
await asyncio.sleep(EDIT_LINKS_INITIAL_DELAY_SEC)
|
await asyncio.sleep(EDIT_LINKS_INITIAL_DELAY_SEC)
|
||||||
method = "editMessageCaption" if has_image else "editMessageText"
|
method = "editMessageCaption" if has_image else "editMessageText"
|
||||||
field = "caption" if has_image else "text"
|
field = "caption" if has_image else "text"
|
||||||
|
entities_field = "caption_entities" if has_image else "entities"
|
||||||
api_url = bot_api_method_url(BOT_TOKEN, method)
|
api_url = bot_api_method_url(BOT_TOKEN, method)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -172,15 +173,17 @@ async def _edit_event_add_links(httpx_client, has_image, message_id, final_text,
|
|||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
if data.get('ok'):
|
if data.get('ok'):
|
||||||
result_text = (data.get('result') or {}).get(field) or ""
|
result = data.get('result') or {}
|
||||||
if published_text_has_links(result_text):
|
result_text = result.get(field) or ""
|
||||||
|
entities = result.get(entities_field) or []
|
||||||
|
if published_message_has_links(result_text, entities):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Ссылки добавлены для {entity_label} "
|
f"Ссылки добавлены для {entity_label} "
|
||||||
f"(попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})"
|
f"(попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})"
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"edit ok, но href= не найден (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
f"edit ok, но ссылки не обнаружены (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
||||||
f"для {entity_label}"
|
f"для {entity_label}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -190,15 +193,15 @@ async def _edit_event_add_links(httpx_client, has_image, message_id, final_text,
|
|||||||
retry_after = data.get('parameters', {}).get('retry_after', 60)
|
retry_after = data.get('parameters', {}).get('retry_after', 60)
|
||||||
raise Exception(f"RetryAfter:{retry_after}")
|
raise Exception(f"RetryAfter:{retry_after}")
|
||||||
if error_code == 400 and "not modified" in error_description:
|
if error_code == 400 and "not modified" in error_description:
|
||||||
logger.warning(
|
logger.info(
|
||||||
f"message is not modified (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
f"Caption уже содержит ссылки для {entity_label} "
|
||||||
f"для {entity_label} — повтор"
|
f"(message is not modified, попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})"
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"API error edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
|
||||||
f"для {entity_label}: {data.get('description')}"
|
|
||||||
)
|
)
|
||||||
|
return True
|
||||||
|
logger.warning(
|
||||||
|
f"API error edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
||||||
|
f"для {entity_label}: {data.get('description')}"
|
||||||
|
)
|
||||||
elif response.status_code == 429:
|
elif response.status_code == 429:
|
||||||
retry_after = 60
|
retry_after = 60
|
||||||
try:
|
try:
|
||||||
@@ -210,15 +213,15 @@ async def _edit_event_add_links(httpx_client, has_image, message_id, final_text,
|
|||||||
try:
|
try:
|
||||||
error_description = (response.json().get('description') or '').lower()
|
error_description = (response.json().get('description') or '').lower()
|
||||||
if "not modified" in error_description:
|
if "not modified" in error_description:
|
||||||
logger.warning(
|
logger.info(
|
||||||
f"message is not modified HTTP 400 (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
f"Caption уже содержит ссылки для {entity_label} "
|
||||||
f"для {entity_label} — повтор"
|
f"(message is not modified HTTP 400, попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})"
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"HTTP 400 edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
|
||||||
f"для {entity_label}: {response.text[:300]}"
|
|
||||||
)
|
)
|
||||||
|
return True
|
||||||
|
logger.warning(
|
||||||
|
f"HTTP 400 edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
||||||
|
f"для {entity_label}: {response.text[:300]}"
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"HTTP 400 edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
f"HTTP 400 edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
||||||
|
|||||||
+28
-2
@@ -58,6 +58,32 @@ EDIT_LINKS_MAX_ATTEMPTS = 5
|
|||||||
EDIT_LINKS_RETRY_INTERVAL_SEC = 2
|
EDIT_LINKS_RETRY_INTERVAL_SEC = 2
|
||||||
|
|
||||||
|
|
||||||
|
def published_message_has_links(text: str, entities=None) -> bool:
|
||||||
|
"""
|
||||||
|
Проверка, что в опубликованном caption/text есть кликабельные ссылки.
|
||||||
|
|
||||||
|
Telegram в ответе editMessage* отдаёт plain text (без href=) и entities типа text_link.
|
||||||
|
"""
|
||||||
|
if entities:
|
||||||
|
for ent in entities:
|
||||||
|
if isinstance(ent, dict):
|
||||||
|
if ent.get("type") == "text_link" and ent.get("url"):
|
||||||
|
return True
|
||||||
|
elif getattr(ent, "type", None) == "text_link" and getattr(ent, "url", None):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not text:
|
||||||
|
return False
|
||||||
|
|
||||||
|
link_markers = (
|
||||||
|
"Оригинал в ВК",
|
||||||
|
"Подписка",
|
||||||
|
"Инфо",
|
||||||
|
"Иду",
|
||||||
|
)
|
||||||
|
return any(marker in text for marker in link_markers)
|
||||||
|
|
||||||
|
|
||||||
def published_text_has_links(text: str) -> bool:
|
def published_text_has_links(text: str) -> bool:
|
||||||
"""В caption/text после edit должны появиться HTML-ссылки."""
|
"""Обратная совместимость; предпочтительно published_message_has_links."""
|
||||||
return bool(text) and "href=" in text
|
return published_message_has_links(text)
|
||||||
|
|||||||
+14
-12
@@ -22,7 +22,7 @@ from telegram_relay import (
|
|||||||
EDIT_LINKS_INITIAL_DELAY_SEC,
|
EDIT_LINKS_INITIAL_DELAY_SEC,
|
||||||
EDIT_LINKS_MAX_ATTEMPTS,
|
EDIT_LINKS_MAX_ATTEMPTS,
|
||||||
EDIT_LINKS_RETRY_INTERVAL_SEC,
|
EDIT_LINKS_RETRY_INTERVAL_SEC,
|
||||||
published_text_has_links,
|
published_message_has_links,
|
||||||
)
|
)
|
||||||
from season_links import subscription_start_link
|
from season_links import subscription_start_link
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ async def download_image(image_url, httpx_client):
|
|||||||
async def _edit_post_add_links(bot, message_id, message_has_image, final_text, vk_post_id):
|
async def _edit_post_add_links(bot, message_id, message_has_image, final_text, vk_post_id):
|
||||||
"""
|
"""
|
||||||
Второй шаг публикации: editMessageCaption/Text со ссылками.
|
Второй шаг публикации: editMessageCaption/Text со ссылками.
|
||||||
Повторяет попытку, пока в ответе API нет href= (защита от гонки после sendPhoto).
|
Повторяет попытку, пока в ответе API нет text_link / меток ссылок.
|
||||||
"""
|
"""
|
||||||
await asyncio.sleep(EDIT_LINKS_INITIAL_DELAY_SEC)
|
await asyncio.sleep(EDIT_LINKS_INITIAL_DELAY_SEC)
|
||||||
|
|
||||||
@@ -171,6 +171,7 @@ async def _edit_post_add_links(bot, message_id, message_has_image, final_text, v
|
|||||||
parse_mode='HTML',
|
parse_mode='HTML',
|
||||||
)
|
)
|
||||||
published_text = edited.caption or ""
|
published_text = edited.caption or ""
|
||||||
|
entities = edited.caption_entities or []
|
||||||
else:
|
else:
|
||||||
edited = await bot.edit_message_text(
|
edited = await bot.edit_message_text(
|
||||||
chat_id=CHANNEL_ID,
|
chat_id=CHANNEL_ID,
|
||||||
@@ -180,8 +181,9 @@ async def _edit_post_add_links(bot, message_id, message_has_image, final_text, v
|
|||||||
disable_web_page_preview=True,
|
disable_web_page_preview=True,
|
||||||
)
|
)
|
||||||
published_text = edited.text or ""
|
published_text = edited.text or ""
|
||||||
|
entities = edited.entities or []
|
||||||
|
|
||||||
if published_text_has_links(published_text):
|
if published_message_has_links(published_text, entities):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Ссылки добавлены для записи VK ID {vk_post_id} "
|
f"Ссылки добавлены для записи VK ID {vk_post_id} "
|
||||||
f"(попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})"
|
f"(попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})"
|
||||||
@@ -189,7 +191,7 @@ async def _edit_post_add_links(bot, message_id, message_has_image, final_text, v
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"editMessage* ok, но href= не найден в ответе "
|
f"editMessage* ok, но ссылки не обнаружены в ответе "
|
||||||
f"(попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) для VK ID {vk_post_id}"
|
f"(попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) для VK ID {vk_post_id}"
|
||||||
)
|
)
|
||||||
except TimedOut as e:
|
except TimedOut as e:
|
||||||
@@ -208,15 +210,15 @@ async def _edit_post_add_links(bot, message_id, message_has_image, final_text, v
|
|||||||
raise RetryAfterException(getattr(e, 'retry_after', 60))
|
raise RetryAfterException(getattr(e, 'retry_after', 60))
|
||||||
err = str(e).lower()
|
err = str(e).lower()
|
||||||
if "not modified" in err:
|
if "not modified" in err:
|
||||||
logger.warning(
|
logger.info(
|
||||||
f"message is not modified (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
f"Caption уже содержит ссылки для VK ID {vk_post_id} "
|
||||||
f"для VK ID {vk_post_id} — повтор"
|
f"(message is not modified, попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})"
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"Ошибка edit ссылок (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
|
||||||
f"для VK ID {vk_post_id}: {e}"
|
|
||||||
)
|
)
|
||||||
|
return True
|
||||||
|
logger.warning(
|
||||||
|
f"Ошибка edit ссылок (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) "
|
||||||
|
f"для VK ID {vk_post_id}: {e}"
|
||||||
|
)
|
||||||
except RetryAfterException:
|
except RetryAfterException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user