import logging from yuxi.channel.extensions.farcaster.client import NeynarClient, NeynarError from yuxi.channel.extensions.farcaster.defaults import CAST_BYTE_LIMIT from yuxi.channel.protocols import OutboundDeliveryMode logger = logging.getLogger(__name__) class FarcasterOutboundAdapter: delivery_mode = OutboundDeliveryMode.DIRECT text_chunk_limit = CAST_BYTE_LIMIT def __init__(self, client_getter, account_id: str = "default"): self._client_getter = client_getter self._account_id = account_id def _get_client(self) -> NeynarClient: client = self._client_getter(self._account_id) if client is None: raise NeynarError("No active Farcaster client") return client async def send_text( self, target_id: str, content: str, *, reply_to_id: str | None = None, thread_id: str | None = None, account_id: str | None = None, channel_id: str | None = None, ) -> None: text = content if len(text.encode("utf-8")) > CAST_BYTE_LIMIT: text = _trim_to_byte_limit(text, CAST_BYTE_LIMIT) parent_hash = reply_to_id or thread_id client = self._get_client() try: result = await client.post_cast( text=text, parent_hash=parent_hash, channel_id=channel_id, ) cast_hash = result.get("cast", {}).get("hash", "") logger.info( "Farcaster cast sent: hash=%s to=%s parent=%s channel=%s", cast_hash, target_id, parent_hash, channel_id, ) except NeynarError as e: logger.error("Farcaster send_text failed: %s", e) async def send_media( self, target_id: str, media_url: str, media_type: str, reply_to_id: str | None = None, thread_id: str | None = None, account_id: str | None = None, channel_id: str | None = None, ) -> None: captions = {"image": "🖼️ Image", "video": "🎬 Video", "audio": "🎵 Audio", "file": "📎 File"} prefix = captions.get(media_type, media_url) text = prefix if len(text.encode("utf-8")) > CAST_BYTE_LIMIT: text = _trim_to_byte_limit(text, CAST_BYTE_LIMIT) parent_hash = reply_to_id or thread_id client = self._get_client() try: await client.post_cast( text=text, parent_hash=parent_hash, embeds=[{"url": media_url}], channel_id=channel_id, ) except NeynarError as e: logger.error("Farcaster send_media failed: type=%s url=%s error=%s", media_type, media_url, e) async def send_reaction( self, target_hash: str, reaction_type: str = "like", ) -> None: client = self._get_client() try: await client.post_reaction(target_hash, reaction_type) except NeynarError as e: logger.error("Farcaster send_reaction failed: %s", e) def _trim_to_byte_limit(text: str, limit: int) -> str: encoded = text.encode("utf-8") if len(encoded) <= limit: return text trimmed = encoded[: limit - 3] return trimmed.decode("utf-8", errors="ignore") + "..."