import logging import httpx logger = logging.getLogger(__name__) LIST_URL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list" GET_URL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get" BATCH_GET_URL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user" GET_TAGS_URL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_corp_tag_list" GROUPCHAT_LIST_URL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/list" GROUPCHAT_GET_URL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/get" class WeComExternalContact: def __init__(self, gateway): self._gateway = gateway self._http: httpx.AsyncClient | None = None @property def _token(self) -> str | None: return self._gateway.access_token if self._gateway else None async def list_customers(self, user_id: str) -> list[str]: token = self._token if not token: return [] url = f"{LIST_URL}?access_token={token}&userid={user_id}" try: resp = await self._get_http().get(url) data = resp.json() if data.get("errcode") == 0: return data.get("external_userid", []) logger.warning("WeCom list_customers failed: %s", data) return [] except Exception: logger.exception("WeCom list_customers error") return [] async def get_customer(self, external_userid: str) -> dict | None: token = self._token if not token: return None url = f"{GET_URL}?access_token={token}&external_userid={external_userid}" try: resp = await self._get_http().get(url) data = resp.json() if data.get("errcode") == 0: return data.get("external_contact") logger.warning("WeCom get_customer failed: %s", data) return None except Exception: logger.exception("WeCom get_customer error") return None async def batch_get_customers(self, user_id: str, cursor: str = "") -> dict: token = self._token if not token: return {} url = f"{BATCH_GET_URL}?access_token={token}" try: resp = await self._get_http().post(url, json={"userid": user_id, "cursor": cursor}) data = resp.json() if data.get("errcode") == 0: return data logger.warning("WeCom batch_get_customers failed: %s", data) return {} except Exception: logger.exception("WeCom batch_get_customers error") return {} async def get_corp_tags(self, tag_ids: list[str] | None = None) -> list[dict]: token = self._token if not token: return [] url = f"{GET_TAGS_URL}?access_token={token}" body: dict = {} if tag_ids: body["tag_id"] = tag_ids try: resp = await self._get_http().post(url, json=body) data = resp.json() if data.get("errcode") == 0: return data.get("tag_group", []) logger.warning("WeCom get_corp_tags failed: %s", data) return [] except Exception: logger.exception("WeCom get_corp_tags error") return [] async def list_groupchats( self, status_filter: int = 0, owner_filter: dict | None = None, cursor: str = "", limit: int = 100, ) -> dict: token = self._token if not token: return {} url = f"{GROUPCHAT_LIST_URL}?access_token={token}" body: dict = {"status_filter": status_filter, "cursor": cursor, "limit": limit} if owner_filter: body["owner_filter"] = owner_filter try: resp = await self._get_http().post(url, json=body) data = resp.json() if data.get("errcode") == 0: return data logger.warning("WeCom list_groupchats failed: %s", data) return {} except Exception: logger.exception("WeCom list_groupchats error") return {} async def get_groupchat(self, chat_id: str) -> dict | None: token = self._token if not token: return None url = f"{GROUPCHAT_GET_URL}?access_token={token}" try: resp = await self._get_http().post(url, json={"chat_id": chat_id}) data = resp.json() if data.get("errcode") == 0: return data.get("group_chat") logger.warning("WeCom get_groupchat failed: %s", data) return None except Exception: logger.exception("WeCom get_groupchat error") return None def _get_http(self) -> httpx.AsyncClient: if self._http is None: self._http = httpx.AsyncClient(timeout=15.0) return self._http async def close(self): if self._http: await self._http.aclose() self._http = None