from __future__ import annotations import json from dataclasses import dataclass from urllib.parse import urljoin from urllib.error import HTTPError, URLError from urllib.request import Request, urlopen from django.conf import settings @dataclass(frozen=True) class MessageAPIResponse: errcode: int errmsg: str raw: dict @property def ok(self) -> bool: return int(self.errcode) == 0 def _request_json( *, url: str, method: str = "GET", payload: dict | None = None, timeout_seconds: float = 10.0, headers: dict | None = None, ) -> dict: data = None request_headers = dict(headers or {}) if payload is not None: data = json.dumps(payload, ensure_ascii=False).encode("utf-8") request_headers.setdefault("Content-Type", "application/json") req = Request(url=url, data=data, headers=request_headers, method=method) try: with urlopen(req, timeout=float(timeout_seconds)) as resp: body = resp.read().decode("utf-8", errors="replace") except HTTPError as exc: body = "" try: body = exc.read().decode("utf-8", errors="replace") except Exception: pass raise RuntimeError(f"Message API HTTPError: status={exc.code}, body={body}") from exc except URLError as exc: raise RuntimeError(f"Message API URLError: {exc}") from exc try: return json.loads(body) if body else {} except Exception as exc: raise RuntimeError(f"Message API 响应不是合法 JSON: {body}") from exc def _get_message_api_base_url() -> str: base_url = str(getattr(settings, "MESSAGE_API_BASE_URL", "") or "").strip().rstrip("/") if not base_url: raise ValueError("MESSAGE_API_BASE_URL 未配置") return base_url def _get_message_api_authorization() -> str: authorization = str(getattr(settings, "MESSAGE_API_AUTHORIZATION", "") or "").strip() if not authorization: raise ValueError("MESSAGE_API_AUTHORIZATION 未配置") return authorization def _build_url(path: str) -> str: return urljoin(f"{_get_message_api_base_url()}/", path.lstrip("/")) def _post_message_api(*, path: str, payload: dict, timeout_seconds: float) -> dict: raw = _request_json( url=_build_url(path), method="POST", payload=payload, timeout_seconds=timeout_seconds, headers={ "Authorization": _get_message_api_authorization(), "Content-Type": "application/json", }, ) return raw def send_message_api_text_message( *, agent_id: int, content: str, user_ids: list[str] | None = None, timeout_seconds: float = 10.0, ) -> MessageAPIResponse: payload = { "agent_id": int(agent_id), "content": str(content or "").strip(), } if user_ids: payload["user_ids"] = user_ids raw = _post_message_api(path="/api/message/send", payload=payload, timeout_seconds=timeout_seconds) return MessageAPIResponse( errcode=int(raw.get("errcode") or 0), errmsg=str(raw.get("errmsg") or ""), raw=raw, ) def send_message_api_news_to_agents( *, agent_ids: list[int], title: str, description: str, url: str, image_url: str, user_ids: list[str] | None = None, timeout_seconds: float = 10.0, ) -> list[dict]: payload = { "agent_ids": [int(agent_id) for agent_id in agent_ids], "title": title, "description": description, "url": url, "image_url": image_url, } if user_ids: payload["user_ids"] = user_ids raw = _post_message_api(path="/api/message/send/news", payload=payload, timeout_seconds=timeout_seconds) return raw.get("results") or []