после миграции группы в супергруппу необходимо обновить её id
Docker Hub / build-and-push (push) Failing after 14s

This commit is contained in:
kislovdm
2026-04-14 11:13:21 +03:00
parent bcf9366cf6
commit 1fd5e4ea39
2 changed files with 121 additions and 22 deletions
+69 -19
View File
@@ -1,4 +1,5 @@
import logging import logging
from collections.abc import Awaitable, Callable
from typing import Any from typing import Any
from max_parser import parse_message from max_parser import parse_message
@@ -6,7 +7,7 @@ from models import ParsedMessage
from pymax import MaxClient from pymax import MaxClient
from pymax.types import PhotoAttach, VideoAttach from pymax.types import PhotoAttach, VideoAttach
from storage import BridgeStorage from storage import BridgeStorage
from telegram_api import TelegramClient from telegram_api import TelegramApiError, TelegramClient
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -80,12 +81,17 @@ class MaxToTelegramBridge:
total_media = len(parsed.image_urls) + len(parsed.video_urls) total_media = len(parsed.image_urls) + len(parsed.video_urls)
if total_media > 1: if total_media > 1:
# Отправляем одним альбомом в Telegram (единое сообщение). # Отправляем одним альбомом в Telegram (единое сообщение).
sent_messages = await self._telegram.send_media_group( target_chat_id, sent_messages = await self._send_with_migration_retry(
chat_id=target_chat_id, target_chat_id=target_chat_id,
image_urls=parsed.image_urls, max_chat_title_norm=normalized,
video_urls=parsed.video_urls, max_chat_title=parsed.chat_name,
caption=text, send_action=lambda chat_id: self._telegram.send_media_group(
reply_to_message_id=reply_telegram_mid, chat_id=chat_id,
image_urls=parsed.image_urls,
video_urls=parsed.video_urls,
caption=text,
reply_to_message_id=reply_telegram_mid,
),
) )
for sent in sent_messages: for sent in sent_messages:
mid = sent.get("message_id") mid = sent.get("message_id")
@@ -109,8 +115,13 @@ class MaxToTelegramBridge:
sent_any = False sent_any = False
if parsed.text.strip() and total_media == 0: if parsed.text.strip() and total_media == 0:
sent = await self._telegram.send_text( target_chat_id, sent = await self._send_with_migration_retry(
target_chat_id, text, reply_to_message_id=reply_telegram_mid target_chat_id=target_chat_id,
max_chat_title_norm=normalized,
max_chat_title=parsed.chat_name,
send_action=lambda chat_id: self._telegram.send_text(
chat_id, text, reply_to_message_id=reply_telegram_mid
),
) )
mid = sent.get("result", {}).get("message_id") if isinstance(sent.get("result"), dict) else None mid = sent.get("result", {}).get("message_id") if isinstance(sent.get("result"), dict) else None
if mid is not None: if mid is not None:
@@ -124,11 +135,16 @@ class MaxToTelegramBridge:
for index, image_url in enumerate(parsed.image_urls): for index, image_url in enumerate(parsed.image_urls):
caption = text if not sent_any and index == 0 else None caption = text if not sent_any and index == 0 else None
sent = await self._telegram.send_photo( target_chat_id, sent = await self._send_with_migration_retry(
target_chat_id, target_chat_id=target_chat_id,
image_url, max_chat_title_norm=normalized,
caption=caption, max_chat_title=parsed.chat_name,
reply_to_message_id=reply_telegram_mid if not sent_any and index == 0 else None, send_action=lambda chat_id: self._telegram.send_photo(
chat_id,
image_url,
caption=caption,
reply_to_message_id=reply_telegram_mid if not sent_any and index == 0 else None,
),
) )
mid = sent.get("result", {}).get("message_id") if isinstance(sent.get("result"), dict) else None mid = sent.get("result", {}).get("message_id") if isinstance(sent.get("result"), dict) else None
if mid is not None: if mid is not None:
@@ -142,11 +158,16 @@ class MaxToTelegramBridge:
for index, video_url in enumerate(parsed.video_urls): for index, video_url in enumerate(parsed.video_urls):
caption = text if not sent_any and index == 0 else None caption = text if not sent_any and index == 0 else None
sent = await self._telegram.send_video( target_chat_id, sent = await self._send_with_migration_retry(
target_chat_id, target_chat_id=target_chat_id,
video_url, max_chat_title_norm=normalized,
caption=caption, max_chat_title=parsed.chat_name,
reply_to_message_id=reply_telegram_mid if not sent_any and index == 0 else None, send_action=lambda chat_id: self._telegram.send_video(
chat_id,
video_url,
caption=caption,
reply_to_message_id=reply_telegram_mid if not sent_any and index == 0 else None,
),
) )
mid = sent.get("result", {}).get("message_id") if isinstance(sent.get("result"), dict) else None mid = sent.get("result", {}).get("message_id") if isinstance(sent.get("result"), dict) else None
if mid is not None: if mid is not None:
@@ -167,6 +188,35 @@ class MaxToTelegramBridge:
len(parsed.video_urls), len(parsed.video_urls),
) )
async def _send_with_migration_retry(
self,
*,
target_chat_id: str,
max_chat_title_norm: str,
max_chat_title: str,
send_action: Callable[[str], Awaitable[Any]],
) -> tuple[str, Any]:
try:
sent = await send_action(target_chat_id)
return target_chat_id, sent
except TelegramApiError as exc:
migrated_chat_id = exc.migrate_to_chat_id
if not migrated_chat_id or migrated_chat_id == str(target_chat_id):
raise
logger.warning(
"Telegram chat %s upgraded to %s for MAX chat '%s'; update route and retry",
target_chat_id,
migrated_chat_id,
max_chat_title,
)
self._storage.set_chat_route(
max_chat_title_norm=max_chat_title_norm,
telegram_chat_id=migrated_chat_id,
telegram_chat_title=max_chat_title,
)
sent = await send_action(migrated_chat_id)
return migrated_chat_id, sent
def _resolve_telegram_reply_to( def _resolve_telegram_reply_to(
self, *, telegram_chat_id: str, max_chat_id: str, parsed: ParsedMessage self, *, telegram_chat_id: str, max_chat_id: str, parsed: ParsedMessage
) -> int | None: ) -> int | None:
+52 -3
View File
@@ -1,11 +1,34 @@
import asyncio import asyncio
import json
from typing import Any from typing import Any
import requests import requests
class TelegramApiError(RuntimeError): class TelegramApiError(RuntimeError):
pass def __init__(
self,
message: str,
*,
method: str | None = None,
status_code: int | None = None,
error_code: int | None = None,
description: str | None = None,
parameters: dict[str, Any] | None = None,
) -> None:
super().__init__(message)
self.method = method
self.status_code = status_code
self.error_code = error_code
self.description = description
self.parameters = parameters or {}
@property
def migrate_to_chat_id(self) -> str | None:
value = self.parameters.get("migrate_to_chat_id")
if value is None:
return None
return str(value)
class TelegramClient: class TelegramClient:
@@ -209,10 +232,36 @@ class TelegramClient:
response = await asyncio.to_thread(_do_request) response = await asyncio.to_thread(_do_request)
if response.status_code >= 400: if response.status_code >= 400:
error_code: int | None = None
description: str | None = None
parameters: dict[str, Any] = {}
try:
payload_data = response.json()
if isinstance(payload_data, dict):
if isinstance(payload_data.get("error_code"), int):
error_code = payload_data.get("error_code")
if isinstance(payload_data.get("description"), str):
description = payload_data.get("description")
if isinstance(payload_data.get("parameters"), dict):
parameters = payload_data.get("parameters", {})
except (json.JSONDecodeError, ValueError):
payload_data = None
raise TelegramApiError( raise TelegramApiError(
f"Telegram HTTP error on {method}: {response.status_code} {response.text}" f"Telegram HTTP error on {method}: {response.status_code} {response.text}",
method=method,
status_code=response.status_code,
error_code=error_code,
description=description,
parameters=parameters,
) )
data = response.json() data = response.json()
if not data.get("ok"): if not data.get("ok"):
raise TelegramApiError(f"Telegram API error on {method}: {data}") raise TelegramApiError(
f"Telegram API error on {method}: {data}",
method=method,
error_code=data.get("error_code") if isinstance(data.get("error_code"), int) else None,
description=data.get("description") if isinstance(data.get("description"), str) else None,
parameters=data.get("parameters") if isinstance(data.get("parameters"), dict) else None,
)
return data return data