218 lines
7.4 KiB
Python
218 lines
7.4 KiB
Python
|
|
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)
|