From ac17043a08264640cbb8566d2f8443ae5b2de350 Mon Sep 17 00:00:00 2001 From: gitadmin Date: Wed, 2 Sep 2026 21:40:29 +0300 Subject: [PATCH] =?UTF-8?q?[ZILANT]=20=D0=BE=D1=82=D0=BB=D0=B0=D0=B4=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B4=D0=BE-=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=81=D1=81?= =?UTF-8?q?=D1=8B=D0=BB=D0=BE=D0=BA=20=D0=B2=20=D0=BF=D0=BE=D1=81=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/version-and-docs.mdc | 2 +- README.md | 2 +- VERSION | 2 +- docs/CHANGELOG.md | 6 ++++ evtg_publish.py | 45 ++++++++++++++++-------------- telegram_relay.py | 30 ++++++++++++++++++-- tg_publish.py | 26 +++++++++-------- 7 files changed, 75 insertions(+), 38 deletions(-) diff --git a/.cursor/rules/version-and-docs.mdc b/.cursor/rules/version-and-docs.mdc index 8316739..41e928a 100644 --- a/.cursor/rules/version-and-docs.mdc +++ b/.cursor/rules/version-and-docs.mdc @@ -20,4 +20,4 @@ alwaysApply: true 5. Не поднимать версию за косметические правки без изменения поведения. -Текущая базовая версия: **1.1.1**. +Текущая базовая версия: **1.1.2**. diff --git a/README.md b/README.md index 5b01d4d..3945315 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Zilant / VOLK — Telegram poster и подписки -**Версия:** 1.1.1 ([`VERSION`](VERSION)) +**Версия:** 1.1.2 ([`VERSION`](VERSION)) Автоматическая публикация постов и событий в Telegram-канал, веб-редактор, бот подписок с сезонными deep link. diff --git a/VERSION b/VERSION index 524cb55..45a1b3f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.1 +1.1.2 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0a901cb..ec49e2b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,12 @@ Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/). Версия проекта — в файле [`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 ### Fixed diff --git a/evtg_publish.py b/evtg_publish.py index d159516..853350d 100644 --- a/evtg_publish.py +++ b/evtg_publish.py @@ -14,7 +14,7 @@ from telegram_relay import ( EDIT_LINKS_INITIAL_DELAY_SEC, EDIT_LINKS_MAX_ATTEMPTS, EDIT_LINKS_RETRY_INTERVAL_SEC, - published_text_has_links, + published_message_has_links, ) 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): - """Edit caption/text со ссылками; повтор до появления href= в ответе API.""" + """Edit caption/text со ссылками; повтор до подтверждения text_link в ответе API.""" await asyncio.sleep(EDIT_LINKS_INITIAL_DELAY_SEC) method = "editMessageCaption" if has_image else "editMessageText" 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) 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: data = response.json() if data.get('ok'): - result_text = (data.get('result') or {}).get(field) or "" - if published_text_has_links(result_text): + result = data.get('result') or {} + result_text = result.get(field) or "" + entities = result.get(entities_field) or [] + if published_message_has_links(result_text, entities): logger.info( f"Ссылки добавлены для {entity_label} " f"(попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})" ) return True logger.warning( - f"edit ok, но href= не найден (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " + f"edit ok, но ссылки не обнаружены (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " f"для {entity_label}" ) 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) raise Exception(f"RetryAfter:{retry_after}") if error_code == 400 and "not modified" in error_description: - logger.warning( - f"message is not modified (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " - f"для {entity_label} — повтор" - ) - else: - logger.warning( - f"API error edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " - f"для {entity_label}: {data.get('description')}" + logger.info( + f"Caption уже содержит ссылки для {entity_label} " + f"(message is not modified, попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})" ) + return True + logger.warning( + f"API error edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " + f"для {entity_label}: {data.get('description')}" + ) elif response.status_code == 429: retry_after = 60 try: @@ -210,15 +213,15 @@ async def _edit_event_add_links(httpx_client, has_image, message_id, final_text, try: error_description = (response.json().get('description') or '').lower() if "not modified" in error_description: - logger.warning( - f"message is not modified HTTP 400 (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " - f"для {entity_label} — повтор" - ) - else: - logger.warning( - f"HTTP 400 edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " - f"для {entity_label}: {response.text[:300]}" + logger.info( + f"Caption уже содержит ссылки для {entity_label} " + f"(message is not modified HTTP 400, попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})" ) + return True + logger.warning( + f"HTTP 400 edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " + f"для {entity_label}: {response.text[:300]}" + ) except Exception: logger.warning( f"HTTP 400 edit (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " diff --git a/telegram_relay.py b/telegram_relay.py index 26f5063..c0ea745 100644 --- a/telegram_relay.py +++ b/telegram_relay.py @@ -58,6 +58,32 @@ EDIT_LINKS_MAX_ATTEMPTS = 5 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: - """В caption/text после edit должны появиться HTML-ссылки.""" - return bool(text) and "href=" in text + """Обратная совместимость; предпочтительно published_message_has_links.""" + return published_message_has_links(text) diff --git a/tg_publish.py b/tg_publish.py index 6114b12..8ccd02d 100644 --- a/tg_publish.py +++ b/tg_publish.py @@ -22,7 +22,7 @@ from telegram_relay import ( EDIT_LINKS_INITIAL_DELAY_SEC, EDIT_LINKS_MAX_ATTEMPTS, EDIT_LINKS_RETRY_INTERVAL_SEC, - published_text_has_links, + published_message_has_links, ) 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): """ Второй шаг публикации: editMessageCaption/Text со ссылками. - Повторяет попытку, пока в ответе API нет href= (защита от гонки после sendPhoto). + Повторяет попытку, пока в ответе API нет text_link / меток ссылок. """ 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', ) published_text = edited.caption or "" + entities = edited.caption_entities or [] else: edited = await bot.edit_message_text( 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, ) 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( f"Ссылки добавлены для записи VK ID {vk_post_id} " 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 logger.warning( - f"editMessage* ok, но href= не найден в ответе " + f"editMessage* ok, но ссылки не обнаружены в ответе " f"(попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) для VK ID {vk_post_id}" ) 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)) err = str(e).lower() if "not modified" in err: - logger.warning( - f"message is not modified (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " - f"для VK ID {vk_post_id} — повтор" - ) - else: - logger.warning( - f"Ошибка edit ссылок (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " - f"для VK ID {vk_post_id}: {e}" + logger.info( + f"Caption уже содержит ссылки для VK ID {vk_post_id} " + f"(message is not modified, попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS})" ) + return True + logger.warning( + f"Ошибка edit ссылок (попытка {attempt}/{EDIT_LINKS_MAX_ATTEMPTS}) " + f"для VK ID {vk_post_id}: {e}" + ) except RetryAfterException: raise except Exception as e: