Spaces:
Runtime error
Runtime error
File size: 7,172 Bytes
09a337f 0b4b9fd b286604 09a337f 0cc1c40 0b4b9fd 0cc1c40 09a337f b286604 0cc1c40 0b4b9fd 0cc1c40 b286604 0cc1c40 b286604 0cc1c40 b286604 0cc1c40 b286604 09a337f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | #!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import time
from collections import defaultdict
from functools import lru_cache
from xml.etree.ElementTree import fromstring
import requests
import uvicorn
from fastapi import FastAPI, Request, Response
from WXBizMsgCrypt3 import WXBizMsgCrypt
from edu_assistant.learning_tasks import QaTask
instruction = """
Act as a c++ professional to answer student aged 5-10 questions. Answer properly and politely.
"""
task = QaTask(instruction=instruction)
app = FastAPI()
TEXT_RESPONSE_TEMPLATE = """
<xml>
<ToUserName>{to_username}</ToUserName>
<FromUserName>{from_username}</FromUserName>
<CreateTime>{create_time}</CreateTime>
<MsgType>text</MsgType>
<Content>{content}</Content>
</xml>
"""
WECHAT_TOKEN = os.environ.get("WECHAT_TOKEN")
WECHAT_AESKEY = os.environ.get("WECHAT_AESKEY")
WECHAT_CORPID = os.environ.get("WECHAT_CORPID")
WECHAT_SECRET = os.environ.get("WECHAT_SECRET")
CODEDOG_PORT = int(os.environ.get("CODEDOG_PORT", 32167))
wxcpt = WXBizMsgCrypt(WECHAT_TOKEN, WECHAT_AESKEY, WECHAT_CORPID)
# 先用个简单的字典存一下,后面可以考虑用 redis
last_reply_time = dict()
server_start_time = int(time.time())
def access_token():
url_base = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"
resp = requests.get(url_base % (WECHAT_CORPID, WECHAT_SECRET)).json()
return resp.get('access_token', '')
def get_chat_history(access_token: str, message_token: str):
url_base = "https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=" + access_token
body = {"token": message_token}
resp = requests.post(url_base, json=body)
"""
[{
"msgid": "",
"open_kfid": "",
"external_userid": "",
"send_time": 1691854816,
"origin": 3,
"msgtype": "text",
"text": {
"content": "哈哈哈哈哈啊"
}
}]
"""
# print("history", json.dumps(resp.json(), indent=2, ensure_ascii=False))
return resp.json()
def reply(external_user_id, open_kfid, content):
url_base = "https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?debug=1&access_token=" + access_token()
body = {
"touser": external_user_id,
"open_kfid": open_kfid,
"msgtype": "text",
"text": {
"content": content
}
}
resp = requests.post(url_base, json=body).json()
return resp.get("errcode", 1) == 0
def extract_messages_blocks(history: list):
text_message_blocks = []
# 这里收集一下 我们能处理的消息
for message_block in history:
open_kfid = message_block.get("open_kfid", None)
external_userid = message_block.get("external_userid", None)
if open_kfid is None or external_userid is None:
print("open_kfid or external_userid is None")
continue
print("message_block", message_block)
if message_block.get("msgtype", None) == "text":
content = message_block["text"]["content"]
# 限制一下要大于服务启动的时间
if message_block["send_time"] > last_reply_time.get(external_userid, server_start_time):
text_message_blocks.append(message_block)
if message_block.get("msgtype", None) == "image":
content = message_block["image"]["media_id"]
print("image block", content)
grouped_blocks = defaultdict(list)
if len(text_message_blocks) == 0:
return grouped_blocks
for message_block in text_message_blocks:
grouped_blocks[message_block["external_userid"]].append(message_block)
return grouped_blocks
# 企业微信同一条用户的消息会发三次回调,内容都一样
# 这里加个 lru_cache 挡一挡,纯属偷懒
@lru_cache()
def handle_message(message_token: str):
history = get_chat_history(access_token=access_token(),
message_token=message_token)
# 实际上这里能拿到所有的用户发来的消息,所以这里要做一下分组
# TODO 翻页
grouped_blocks = extract_messages_blocks(history["msg_list"])
if len(grouped_blocks) == 0:
return
for external_userid, text_message_blocks in grouped_blocks.items():
content = "\n".join([message_block["text"]["content"] for message_block in text_message_blocks])
open_kfid = text_message_blocks[-1].get("open_kfid", None)
last_reply_time[text_message_blocks[-1]["external_userid"]] = text_message_blocks[-1]["send_time"]
# print(open_kfid, external_userid, content)
result = task.ask(content, session=False)
print("handle_message", result)
"""
{
"input": "",
"chat_history": "",
"text": ""
}
"""
reply(external_userid, open_kfid, result["text"])
@app.get("/")
async def verify(msg_signature: str,
timestamp: str,
nonce: str,
echostr: str):
'''
验证配置是否成功,处理get请求
:param msg_signature:
:param timestamp:
:param nonce:
:param echostr:
:return:
'''
ret, sEchoStr = wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr)
if ret == 0:
return Response(content=sEchoStr.decode('utf-8'))
else:
print(sEchoStr)
@app.post("/")
async def recv(msg_signature: str,
timestamp: str,
nonce: str,
request: Request):
'''
接收用户消息,可进行被动响应
:param msg_signature:
:param timestamp:
:param nonce:
:param request:
:return:
'''
body = await request.body()
ret, msg = wxcpt.DecryptMsg(body.decode(
'utf-8'), msg_signature, timestamp, nonce)
decrypt_data = {}
for node in list(fromstring(msg.decode('utf-8'))):
decrypt_data[node.tag] = node.text
# 解析后得到的decrypt_data:
# {"ToUserName":"企业号", "FromUserName":"发送者用户名", "CreateTime":"发送时间",
# "Content":"用户发送的内容", "MsgId":"唯一id,需要针对此id做出响应", "AagentID": "应用id"}
print("decrypt_data", decrypt_data)
message_token = decrypt_data["Token"]
# 真正进行回复的地方
handle_message(message_token)
# 这里只需要响应一下回调函数即可,不需要返回数据
return Response(content="success")
# resp_data = TEXT_RESPONSE_TEMPLATE.format(to_username=decrypt_data.get("ToUserName", ""),
# from_username=decrypt_data.get(
# "FromUserName", ""),
# create_time=decrypt_data.get(
# "CreateTime", ""),
# content="帅得一逼",)
# ret, send_msg = wxcpt.EncryptMsg(sReplyMsg=resp_data, sNonce=nonce)
# if ret == 0:
# return Response(content=send_msg)
# else:
# print(send_msg)
if __name__ == "__main__":
uvicorn.run("web:app", port=CODEDOG_PORT, host='0.0.0.0', reload=False)
|