import hashlib import hmac import json import logging import time import httpx logger = logging.getLogger(__name__) GATEWAY_URL = "https://eco.taobao.com/router/rest" SANDBOX_GATEWAY_URL = "https://gw.api.tbsandbox.com/router/rest" class TaobaoAPIError(Exception): def __init__(self, code: str, msg: str, sub_code: str = "", sub_msg: str = ""): self.code = code self.msg = msg self.sub_code = sub_code self.sub_msg = sub_msg super().__init__(f"[{code}] {msg}" + (f" ({sub_code}: {sub_msg})" if sub_code else "")) class TaobaoClient: def __init__( self, app_key: str, app_secret: str, sign_method: str = "md5", sandbox: bool = False, timeout: int = 10, ): self.app_key = app_key self.app_secret = app_secret self.sign_method = sign_method self.gateway = SANDBOX_GATEWAY_URL if sandbox else GATEWAY_URL self.timeout = timeout def _sign(self, params: dict) -> str: sorted_keys = sorted(k for k in params if k != "sign" and params[k] is not None) raw = "".join(f"{k}{params[k]}" for k in sorted_keys) raw = f"{self.app_secret}{raw}{self.app_secret}" if self.sign_method == "hmac-sha256": return ( hmac.new( self.app_secret.encode("utf-8"), raw.encode("utf-8"), hashlib.sha256, ) .hexdigest() .upper() ) return hashlib.md5(raw.encode("utf-8")).hexdigest().upper() def _build_payload(self, method: str, params: dict, session: str | None = None) -> dict: payload = { "method": method, "app_key": self.app_key, "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), "format": "json", "v": "2.0", "sign_method": self.sign_method, **params, } if session: payload["session"] = session payload["sign"] = self._sign(payload) return payload async def execute( self, method: str, params: dict | None = None, session: str | None = None, ) -> dict: payload = self._build_payload(method, params or {}, session) async with httpx.AsyncClient(timeout=self.timeout) as http: resp = await http.post(self.gateway, data=payload) resp.raise_for_status() data = resp.json() if "error_response" in data: err = data["error_response"] raise TaobaoAPIError( code=err.get("code", "unknown"), msg=err.get("msg", ""), sub_code=err.get("sub_code", ""), sub_msg=err.get("sub_msg", ""), ) return data async def get_token(self, code: str, redirect_uri: str) -> dict: return await self.execute( "taobao.top.auth.token.create", {"code": code, "redirect_uri": redirect_uri}, ) async def refresh_token(self, refresh_token: str) -> dict: return await self.execute( "taobao.top.auth.token.refresh", {"refresh_token": refresh_token}, ) async def send_customer_message( self, to_user: str, content: str, msg_type: int = 0, media_id: str | None = None, session: str | None = None, ) -> dict: context = {"text": content} if msg_type == 0 else {"media_id": media_id or ""} return await self.execute( "taobao.openim.custmsg.push", { "to_user": to_user, "msg_type": msg_type, "context": json.dumps(context, ensure_ascii=False), }, session=session, ) async def get_trade_fullinfo(self, tid: int, session: str) -> dict: return await self.execute( "taobao.trade.fullinfo.get", { "tid": tid, "fields": "tid,type,status,payment,orders,logistics", }, session=session, ) async def get_logistics_trace(self, tid: int, session: str) -> dict: return await self.execute( "taobao.logistics.trace.search", {"tid": tid}, session=session, ) async def get_userservice(self, user_id: str, session: str) -> dict: return await self.execute( "taobao.openim.userservice.get", {"user_id": user_id}, session=session, ) async def qianniu_sellerorder_query(self, buyer_nick: str, session: str) -> dict: return await self.execute( "taobao.qianniu.sellerorder.buyer.query", {"buyer_nick": buyer_nick}, session=session, ) async def qianniu_refund_get(self, refund_id: str, session: str) -> dict: return await self.execute( "taobao.qianniu.refund.get", {"refund_id": refund_id}, session=session, ) async def qianniu_buyer_tag_get(self, buyer_nick: str, session: str) -> dict: return await self.execute( "taobao.qianniu.buyer.tag.get", {"buyer_nick": buyer_nick}, session=session, ) async def qianniu_coupon_buyer_get(self, buyer_nick: str, session: str) -> dict: return await self.execute( "taobao.qianniu.coupon.buyer.get", {"buyer_nick": buyer_nick}, session=session, ) async def qianniu_cloudkefu_forward(self, buyer_nick: str, to_nick: str, session: str) -> dict: return await self.execute( "taobao.qianniu.cloudkefu.forward", {"buyer_nick": buyer_nick, "to_nick": to_nick}, session=session, ) async def get_item(self, item_id: int, session: str) -> dict: return await self.execute( "taobao.item.get", {"item_id": item_id, "fields": "title,price,pic_url,desc"}, session=session, ) async def get_chatlogs(self, user_id: str, begin: str, end: str, session: str) -> dict: return await self.execute( "taobao.openim.chatlogs.get", {"user_id": user_id, "begin": begin, "end": end}, session=session, )