import logging import httpx from yuxi.channel.extensions.clickup.config import ClickUpConfigAdapter from yuxi.channel.extensions.clickup.types import OutboundResult logger = logging.getLogger(__name__) API_V3_BASE = "https://api.clickup.com/api/v3" async def get_message( workspace_id: str, message_id: str, api_token: str, ) -> dict | None: url = f"{API_V3_BASE}/workspaces/{workspace_id}/chat/messages/{message_id}" headers = {"Authorization": api_token} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.get(url, headers=headers) if resp.status_code == 200: return resp.json() logger.error("ClickUp get_message failed: status=%d", resp.status_code) return None except Exception as e: logger.exception("ClickUp get_message exception: %s", e) return None async def list_channel_messages( workspace_id: str, channel_id: str, api_token: str, limit: int = 50, cursor: str | None = None, ) -> dict: url = f"{API_V3_BASE}/workspaces/{workspace_id}/chat/channels/{channel_id}/messages" params: dict = {"limit": min(limit, 100)} if cursor: params["cursor"] = cursor headers = {"Authorization": api_token} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.get(url, params=params, headers=headers) if resp.status_code == 200: return resp.json() logger.error("ClickUp list_channel_messages failed: status=%d", resp.status_code) return {"messages": []} except Exception as e: logger.exception("ClickUp list_channel_messages exception: %s", e) return {"messages": []} async def list_dm_messages( workspace_id: str, dm_id: str, api_token: str, limit: int = 50, cursor: str | None = None, ) -> dict: url = f"{API_V3_BASE}/workspaces/{workspace_id}/chat/dm/{dm_id}/messages" params: dict = {"limit": min(limit, 100)} if cursor: params["cursor"] = cursor headers = {"Authorization": api_token} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.get(url, params=params, headers=headers) if resp.status_code == 200: return resp.json() logger.error("ClickUp list_dm_messages failed: status=%d", resp.status_code) return {"messages": []} except Exception as e: logger.exception("ClickUp list_dm_messages exception: %s", e) return {"messages": []} async def send_channel_message( workspace_id: str, channel_id: str, content: str, api_token: str, reply_to_id: str | None = None, ) -> OutboundResult: url = f"{API_V3_BASE}/workspaces/{workspace_id}/chat/channels/{channel_id}/messages" headers = {"Authorization": api_token, "Content-Type": "application/json"} payload: dict = { "type": "message", "content": content[:40000], "content_format": "text/md", } async with httpx.AsyncClient(timeout=30.0) as client: try: resp = await client.post(url, json=payload, headers=headers) if resp.status_code == 201: data = resp.json() return OutboundResult(success=True, message_id=data.get("id", "")) error_text = resp.text[:500] logger.error("ClickUp send_channel_message failed: status=%d body=%s", resp.status_code, error_text) return OutboundResult(success=False, error=f"http_{resp.status_code}", detail=error_text) except Exception as e: logger.exception("ClickUp send_channel_message exception: %s", e) return OutboundResult(success=False, error="exception", detail=str(e)) async def list_channels( workspace_id: str, api_token: str, cursor: str | None = None, ) -> dict: url = f"{API_V3_BASE}/workspaces/{workspace_id}/chat/channels" params: dict = {} if cursor: params["cursor"] = cursor headers = {"Authorization": api_token} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.get(url, params=params, headers=headers) if resp.status_code == 200: return resp.json() logger.error("ClickUp list_channels failed: status=%d", resp.status_code) return {"channels": []} except Exception as e: logger.exception("ClickUp list_channels exception: %s", e) return {"channels": []} async def create_channel( workspace_id: str, api_token: str, name: str, *, visibility: str = "public", member_ids: list[str] | None = None, ) -> dict: url = f"{API_V3_BASE}/workspaces/{workspace_id}/chat/channels" headers = {"Authorization": api_token, "Content-Type": "application/json"} payload: dict = { "name": name, "visibility": visibility, } if member_ids: payload["members"] = member_ids async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.post(url, json=payload, headers=headers) if resp.status_code in (200, 201): return resp.json() error_text = resp.text[:500] logger.error("ClickUp create_channel failed: status=%d body=%s", resp.status_code, error_text) raise RuntimeError(f"创建频道失败 (HTTP {resp.status_code}): {error_text}") except Exception: logger.exception("ClickUp create_channel exception") raise async def get_channel_members( workspace_id: str, api_token: str, channel_id: str, ) -> list[dict]: url = f"{API_V3_BASE}/workspaces/{workspace_id}/chat/channels/{channel_id}/members" headers = {"Authorization": api_token} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.get(url, headers=headers) if resp.status_code == 200: data = resp.json() return data.get("members", []) error_text = resp.text[:500] logger.error("ClickUp get_channel_members failed: status=%d", resp.status_code) raise RuntimeError(f"获取频道成员失败 (HTTP {resp.status_code}): {error_text}") except Exception: logger.exception("ClickUp get_channel_members exception") raise async def create_dm( workspace_id: str, api_token: str, member_ids: list[str], ) -> dict: url = f"{API_V3_BASE}/workspaces/{workspace_id}/chat/dm" headers = {"Authorization": api_token, "Content-Type": "application/json"} payload = {"members": member_ids} async with httpx.AsyncClient(timeout=15.0) as client: try: resp = await client.post(url, json=payload, headers=headers) if resp.status_code in (200, 201): return resp.json() error_text = resp.text[:500] logger.error("ClickUp create_dm failed: status=%d body=%s", resp.status_code, error_text) raise RuntimeError(f"创建私信失败 (HTTP {resp.status_code}): {error_text}") except Exception: logger.exception("ClickUp create_dm exception") raise async def resolve_account(account_id: str | None = None) -> dict | None: adapter = ClickUpConfigAdapter() aid = account_id or adapter.default_account_id({}) return await adapter.resolve_account(aid)