Add BoxIMClient with get_credentials and send_p2p_message methods, using Huihui platform MD5 signing mechanism (sorted keys, 24h timestamp, 12-char alphanumeric nonce). Includes full test coverage.
75 lines
2.7 KiB
Python
75 lines
2.7 KiB
Python
"""盒子 IM 客户端 — 封装网易云信 IM 接口调用"""
|
|
import hashlib
|
|
import random
|
|
import string
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
import httpx
|
|
|
|
|
|
class BoxIMClient:
|
|
"""盒子 IM 客户端,通过会会平台网关调用网易云信 IM"""
|
|
|
|
def __init__(self, config: dict):
|
|
self.base_url = config.get("HUIHUI_IM_BASE_URL", "http://192.168.1.200:60040")
|
|
self.app_id = config.get("HUIHUI_APP_ID", "")
|
|
self.access_id = config.get("HUIHUI_ACCESS_ID", "")
|
|
self.access_secret = config.get("HUIHUI_ACCESS_SECRET", "")
|
|
|
|
def _build_sign_params(self, extra: dict) -> dict:
|
|
"""构建带签名的请求参数(复用 news_service 签名模式)"""
|
|
nonce = "".join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
|
timestamp = datetime.now().strftime("%Y%m%d%H%M%S") # 24小时制
|
|
params = {
|
|
"appId": self.app_id,
|
|
"accessId": self.access_id,
|
|
"nonce": nonce,
|
|
"timestamp": timestamp,
|
|
**extra,
|
|
}
|
|
# 计算签名 — 排序 key, 过滤空值, 拼接后加 accessSecret, MD5 大写
|
|
keys = sorted(params.keys())
|
|
sign_parts = []
|
|
for k in keys:
|
|
v = params.get(k)
|
|
if v and v != "" and v != []:
|
|
sign_parts.append(f"{k}={v}")
|
|
sign_str = "&".join(sign_parts) + f"&accessSecret={self.access_secret}"
|
|
signature = hashlib.md5(sign_str.encode("utf-8")).hexdigest().upper()
|
|
params["signature"] = signature
|
|
params["signType"] = "MD5"
|
|
params["signVersion"] = "1.0"
|
|
return params
|
|
|
|
async def get_credentials(self, user_id: str) -> Optional[dict]:
|
|
"""获取用户的网易云信 IM 凭证 (accid, token)"""
|
|
params = self._build_sign_params({"userId": user_id})
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|
r = await client.post(
|
|
f"{self.base_url}/box/netease",
|
|
params=params,
|
|
)
|
|
data = r.json()
|
|
if data.get("code") in (0, 200):
|
|
return data.get("data", {})
|
|
return None
|
|
|
|
async def send_p2p_message(
|
|
self, from_accid: str, to_accid: str, content: str
|
|
) -> bool:
|
|
"""发送单聊消息(文本)"""
|
|
params = self._build_sign_params({
|
|
"from": from_accid,
|
|
"to": to_accid,
|
|
"msgType": "text",
|
|
"content": content,
|
|
})
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|
r = await client.post(
|
|
f"{self.base_url}/box/message/send/p2p",
|
|
params=params,
|
|
)
|
|
data = r.json()
|
|
return data.get("code") in (0, 200)
|