surfaces/adapter/max/converter.py

151 lines
4.2 KiB
Python

"""MAX Bot API payloads -> core Incoming* types."""
from __future__ import annotations
from typing import Any
from core.protocol import Attachment, IncomingCallback, IncomingCommand, IncomingMessage
def incoming_from_text_commands(
*,
text: str,
max_user_id: str,
platform_chat_id: str,
attachments: list[Attachment],
) -> IncomingMessage | IncomingCommand | IncomingCallback:
"""Парсинг текста: только slash-команды (как в Telegram), обычное сообщение иначе."""
stripped = text.strip()
proto = stripped.lower()
if proto in {"/yes"}:
return IncomingCallback(
user_id=max_user_id,
platform="max",
chat_id=platform_chat_id,
action="confirm",
payload={},
)
if proto in {"/no"}:
return IncomingCallback(
user_id=max_user_id,
platform="max",
chat_id=platform_chat_id,
action="cancel",
payload={},
)
if not stripped.startswith("/"):
return IncomingMessage(
user_id=max_user_id,
platform="max",
chat_id=platform_chat_id,
text=text,
attachments=attachments,
reply_to=None,
)
raw = stripped[1:]
parts = raw.split(maxsplit=1)
name = (parts[0] or "").lower()
tail = parts[1] if len(parts) > 1 else ""
return IncomingCommand(
user_id=max_user_id,
platform="max",
chat_id=platform_chat_id,
command=name,
args=tail.split() if tail else [],
)
def incoming_from_message_callback_payload(
*,
max_user_id: str,
platform_chat_id: str,
payload_raw: str | None,
callback_message_id: str | None,
) -> IncomingCallback | None:
if not payload_raw:
return None
if payload_raw in {"confirm", "cancel", "toggle_skill"}:
return IncomingCallback(
user_id=max_user_id,
platform="max",
chat_id=platform_chat_id,
action=payload_raw,
payload={"message_id": callback_message_id or ""},
)
return IncomingCallback(
user_id=max_user_id,
platform="max",
chat_id=platform_chat_id,
action="max_callback",
payload={"payload": payload_raw, "message_id": callback_message_id},
)
def attachment_from_max_dict(raw: dict[str, Any]) -> tuple[Attachment, dict[str, Any]] | None:
"""Return core Attachment placeholder + raw attachment for download."""
kind = raw.get("type")
payload = raw.get("payload")
if not isinstance(kind, str) or not isinstance(payload, dict):
return None
url = payload.get("url")
if not isinstance(url, str):
url = ""
token = payload.get("token")
filename = "attachment.bin"
mapped = "document"
mime: str | None = None
if kind == "image":
mapped = "image"
filename = "image.jpg"
elif kind == "video":
mapped = "video"
filename = "video.mp4"
elif kind == "audio":
mapped = "audio"
mime = payload.get("mime_type") if isinstance(payload.get("mime_type"), str) else "audio/mpeg"
filename = "audio.bin"
elif kind == "file":
fname = payload.get("filename")
filename = fname if isinstance(fname, str) and fname else "file.bin"
mapped = "document"
else:
return None
attachment = Attachment(
type=mapped,
url=url or None,
content=None,
filename=filename,
mime_type=mime,
)
meta = dict(raw)
if token:
meta["_download_token_hint"] = token
return attachment, meta
def collect_max_attachments(message_body: dict[str, Any]) -> tuple[list[Attachment], list[dict[str, Any]]]:
attachments = message_body.get("attachments")
if not isinstance(attachments, list):
return [], []
core_list: list[Attachment] = []
raw_list: list[dict[str, Any]] = []
for item in attachments:
if isinstance(item, dict):
parsed = attachment_from_max_dict(item)
if parsed is None:
continue
core_a, raw_a = parsed
core_list.append(core_a)
raw_list.append(raw_a)
return core_list, raw_list