from __future__ import annotations from abc import ABC, abstractmethod from collections.abc import AsyncIterator, Awaitable, Callable from typing import Any, ClassVar from yuxi.channels.capabilities import CAPS_SIMPLE_TEXT, ChannelCapabilities from yuxi.channels.meta import ChannelMeta from yuxi.channels.models import ( ChannelMessage, ChannelResponse, ChannelType, DeliveryResult, HealthStatus, ) class BaseChannelAdapter(ABC): channel_id: ClassVar[str] channel_type: ClassVar[ChannelType] text_chunk_limit: ClassVar[int] = 4096 supports_markdown: ClassVar[bool] = False supports_streaming: ClassVar[bool] = False streaming_modes: ClassVar[list[str]] = ["off"] max_media_size_mb: ClassVar[int] = 100 webhook_path: ClassVar[str | None] = None capabilities: ClassVar[ChannelCapabilities] = CAPS_SIMPLE_TEXT meta: ClassVar[ChannelMeta] = ChannelMeta(id="", label="") def __init__(self, config: dict[str, Any] | None = None): self.config = config or {} self._message_handler: Callable[[ChannelMessage], Awaitable[None]] | None = None self._stream_state: dict[str, int] = {} def _get_stream_state(self, chat_id: str, msg_id: str) -> int: return self._stream_state.get(f"{chat_id}:{msg_id}", 0) def _set_stream_state(self, chat_id: str, msg_id: str, state: int) -> None: self._stream_state[f"{chat_id}:{msg_id}"] = state def _clear_stream_state(self, chat_id: str, msg_id: str) -> None: self._stream_state.pop(f"{chat_id}:{msg_id}", None) @abstractmethod async def connect(self) -> None: ... @abstractmethod async def disconnect(self) -> None: ... @abstractmethod async def send(self, response: ChannelResponse) -> DeliveryResult: ... async def receive(self) -> AsyncIterator[ChannelMessage]: return yield # type: ignore[misc] @abstractmethod def normalize_inbound(self, raw: Any) -> ChannelMessage: ... @abstractmethod def format_outbound(self, response: ChannelResponse) -> Any: ... @abstractmethod async def health_check(self) -> HealthStatus: ... def on_message(self, handler: Callable[[ChannelMessage], Awaitable[None]]) -> None: self._message_handler = handler async def _handle_message(self, message: ChannelMessage) -> None: if self._message_handler: await self._message_handler(message) async def send_media(self, chat_id: str, media_type: str, data: Any) -> DeliveryResult: raise NotImplementedError async def edit_message(self, chat_id: str, msg_id: str, content: str) -> DeliveryResult: raise NotImplementedError async def delete_message(self, chat_id: str, msg_id: str) -> DeliveryResult: raise NotImplementedError async def send_reaction(self, chat_id: str, msg_id: str, emoji: str) -> DeliveryResult: raise NotImplementedError async def send_stream_chunk(self, chat_id: str, msg_id: str, chunk: str, finished: bool) -> DeliveryResult: identity = self._build_stream_identity(chat_id, msg_id) response = ChannelResponse(identity=identity, content=chunk) return await self.send(response) def _build_stream_identity(self, chat_id: str, msg_id: str) -> Any: from yuxi.channels.models import ChannelIdentity return ChannelIdentity( channel_id=self.channel_id, channel_type=self.channel_type, channel_user_id="", channel_chat_id=chat_id, channel_message_id=msg_id, ) async def verify_webhook_signature(self, headers: dict, body: bytes) -> bool: return True async def _refresh_token_if_needed(self) -> bool: return True async def pre_connect(self) -> dict: return {}