You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
4.6 KiB
128 lines
4.6 KiB
import httpx
|
|
import logging
|
|
import uuid
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_WECOM_ACCESS_TOKEN: dict = {"token": None, "expires_at": 0}
|
|
|
|
|
|
def _get_access_token(corp_id: str, app_secret: str) -> str | None:
|
|
if not corp_id or not app_secret:
|
|
logger.warning("WECOM_CORP_ID 或 WECOM_APP_SECRET 未配置,无法发送企微通知")
|
|
return None
|
|
|
|
import time
|
|
now = time.time()
|
|
if _WECOM_ACCESS_TOKEN["token"] and _WECOM_ACCESS_TOKEN["expires_at"] > now + 60:
|
|
return _WECOM_ACCESS_TOKEN["token"]
|
|
|
|
try:
|
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corp_id}&corpsecret={app_secret}"
|
|
resp = httpx.get(url, timeout=10)
|
|
data = resp.json()
|
|
if data.get("errcode") == 0:
|
|
_WECOM_ACCESS_TOKEN["token"] = data["access_token"]
|
|
_WECOM_ACCESS_TOKEN["expires_at"] = now + data.get("expires_in", 7200) - 300
|
|
return _WECOM_ACCESS_TOKEN["token"]
|
|
else:
|
|
logger.error(f"获取企微 token 失败: {data}")
|
|
return None
|
|
except Exception as e:
|
|
logger.error(f"请求企微 token 异常: {e}")
|
|
return None
|
|
|
|
|
|
def _get_config():
|
|
from config import settings
|
|
return settings.WECOM_CORP_ID, settings.WECOM_APP_SECRET
|
|
|
|
|
|
def send_notification(to_user: str, message: str, msg_type: str = "text") -> str:
|
|
corp_id, app_secret = _get_config()
|
|
token = _get_access_token(corp_id, app_secret)
|
|
if not token:
|
|
return "企业微信通知发送失败: 未配置 WECOM_CORP_ID/WECOM_APP_SECRET 或获取 access_token 失败"
|
|
|
|
try:
|
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={token}"
|
|
|
|
if msg_type == "textcard":
|
|
body = {
|
|
"touser": to_user,
|
|
"msgtype": "textcard",
|
|
"agentid": 0,
|
|
"textcard": {
|
|
"title": "企业AI平台通知",
|
|
"description": message,
|
|
"url": "",
|
|
},
|
|
}
|
|
else:
|
|
body = {
|
|
"touser": to_user,
|
|
"msgtype": "text",
|
|
"agentid": 0,
|
|
"text": {"content": message},
|
|
}
|
|
|
|
resp = httpx.post(url, json=body, timeout=10)
|
|
data = resp.json()
|
|
if data.get("errcode") == 0:
|
|
return f"企业微信通知已成功发送至 {to_user}"
|
|
else:
|
|
logger.error(f"企微消息发送失败: {data}")
|
|
return f"企业微信通知发送失败: {data.get('errmsg', '未知错误')}"
|
|
except Exception as e:
|
|
logger.error(f"企微消息发送异常: {e}")
|
|
return f"企业微信通知发送失败: {e}"
|
|
|
|
|
|
def query_wecom_user(user_id: str) -> str:
|
|
corp_id, app_secret = _get_config()
|
|
token = _get_access_token(corp_id, app_secret)
|
|
if not token:
|
|
return "企业微信用户查询失败: 未配置或 access_token 获取失败"
|
|
|
|
try:
|
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token={token}&userid={user_id}"
|
|
resp = httpx.get(url, timeout=10)
|
|
data = resp.json()
|
|
if data.get("errcode") == 0:
|
|
user = data
|
|
return f"用户 {user.get('name', user_id)} - 部门: {user.get('department', [])} - 职位: {user.get('position', '未知')}"
|
|
else:
|
|
return f"企业微信用户查询失败: {data.get('errmsg', '未知错误')}"
|
|
except Exception as e:
|
|
return f"企业微信用户查询失败: {e}"
|
|
|
|
|
|
def send_wecom_group_message(message: str, group_id: str | None = None, msg_type: str = "text") -> str:
|
|
corp_id, app_secret = _get_config()
|
|
token = _get_access_token(corp_id, app_secret)
|
|
if not token:
|
|
return "企业微信群消息发送失败: 未配置或 access_token 获取失败"
|
|
|
|
try:
|
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token={token}"
|
|
|
|
body = {
|
|
"chatid": group_id,
|
|
"msgtype": msg_type,
|
|
}
|
|
if msg_type == "text":
|
|
body["text"] = {"content": message}
|
|
elif msg_type == "markdown":
|
|
body["markdown"] = {"content": message}
|
|
|
|
resp = httpx.post(url, json=body, timeout=10)
|
|
data = resp.json()
|
|
if data.get("errcode") == 0:
|
|
return f"企业微信群消息已成功发送至群 {group_id}"
|
|
else:
|
|
return f"企业微信群消息发送失败: {data.get('errmsg', '未知错误')}"
|
|
except Exception as e:
|
|
return f"企业微信群消息发送失败: {e}"
|
|
|
|
|
|
__all__ = ["send_notification", "query_wecom_user", "send_wecom_group_message"]
|