cloud26 commited on
Commit
09a337f
·
unverified ·
1 Parent(s): 739dd38

Feature/add wechat support (#24)

Browse files
README.md CHANGED
@@ -39,9 +39,17 @@ app_port: 7860
39
  | REDIS_PORT | No | 6379 | redis port |
40
  | REDIS_PASSWORD | No | | redis password |
41
  | REDIS_SSL | No | | connect use ssl if not blank |
 
 
 
 
42
 
43
  ## setup
44
 
45
  - install python 3.10+
46
  - install poetry 1.5.1+
47
  - run: `poetry install --with dev`
 
 
 
 
 
39
  | REDIS_PORT | No | 6379 | redis port |
40
  | REDIS_PASSWORD | No | | redis password |
41
  | REDIS_SSL | No | | connect use ssl if not blank |
42
+ | WECHAT_TOKEN | No | | wechat token |
43
+ | WECHAT_AESKEY | No | | wechat aeskey |
44
+ | WECHAT_COPRID | No | | wechat corpid |
45
+ | WECHAT_SECRET | No | | wechat secret |
46
 
47
  ## setup
48
 
49
  - install python 3.10+
50
  - install poetry 1.5.1+
51
  - run: `poetry install --with dev`
52
+
53
+
54
+ # How to start wechat-server
55
+ - python3 wechat-server/web.py
poetry.lock CHANGED
The diff for this file is too large to render. See raw diff
 
pyproject.toml CHANGED
@@ -20,6 +20,7 @@ tiktoken = "^0.4.0"
20
  gradio = "^3.37.0"
21
  redis = "^4.6.0"
22
  pydantic-redis = "^0.4.3"
 
23
 
24
  [tool.poetry.group.dev]
25
  optional = true
 
20
  gradio = "^3.37.0"
21
  redis = "^4.6.0"
22
  pydantic-redis = "^0.4.3"
23
+ pycryptodome = "^3.18.0"
24
 
25
  [tool.poetry.group.dev]
26
  optional = true
webui/ui.py CHANGED
@@ -1,5 +1,4 @@
1
  import gradio as gr
2
- import uvicorn
3
  from fastapi import FastAPI
4
 
5
  from edu_assistant import version
 
1
  import gradio as gr
 
2
  from fastapi import FastAPI
3
 
4
  from edu_assistant import version
wechat-server/WXBizMsgCrypt3.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # -*- encoding:utf-8 -*-
3
+
4
+ """ 对企业微信发送给企业后台的消息加解密示例代码.
5
+ @copyright: Copyright (c) 1998-2014 Tencent Inc.
6
+ """
7
+ import base64
8
+ import hashlib
9
+
10
+ # ------------------------------------------------------------------------
11
+ import logging
12
+ import random
13
+ import socket
14
+ import struct
15
+ import time
16
+ import xml.etree.cElementTree as ET
17
+
18
+ import ierror
19
+ from Crypto.Cipher import AES
20
+
21
+ """
22
+ 关于Crypto.Cipher模块,ImportError: No module named 'Crypto'解决方案
23
+ 请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。
24
+ 下载后,按照README中的“Installation”小节的提示进行pycrypto安装。
25
+ """
26
+
27
+
28
+ class FormatException(Exception):
29
+ pass
30
+
31
+
32
+ def throw_exception(message, exception_class=FormatException):
33
+ """my define raise exception function"""
34
+ raise exception_class(message)
35
+
36
+
37
+ class SHA1:
38
+ """计算企业微信的消息签名接口"""
39
+
40
+ def getSHA1(self, token, timestamp, nonce, encrypt):
41
+ """用SHA1算法生成安全签名
42
+ @param token: 票据
43
+ @param timestamp: 时间戳
44
+ @param encrypt: 密文
45
+ @param nonce: 随机字符串
46
+ @return: 安全签名
47
+ """
48
+ try:
49
+ sortlist = [token, timestamp, nonce, encrypt]
50
+ sortlist.sort()
51
+ sha = hashlib.sha1()
52
+ sha.update("".join(sortlist).encode())
53
+ return ierror.WXBizMsgCrypt_OK, sha.hexdigest()
54
+ except Exception as e:
55
+ logger = logging.getLogger()
56
+ logger.error(e)
57
+ return ierror.WXBizMsgCrypt_ComputeSignature_Error, None
58
+
59
+
60
+ class XMLParse:
61
+ """提供提取消息格式中的密文及生成回复消息格式的接口"""
62
+
63
+ # xml消息模板
64
+ AES_TEXT_RESPONSE_TEMPLATE = """<xml>
65
+ <Encrypt><![CDATA[%(msg_encrypt)s]]></Encrypt>
66
+ <MsgSignature><![CDATA[%(msg_signaturet)s]]></MsgSignature>
67
+ <TimeStamp>%(timestamp)s</TimeStamp>
68
+ <Nonce><![CDATA[%(nonce)s]]></Nonce>
69
+ </xml>"""
70
+
71
+ def extract(self, xmltext):
72
+ """提取出xml数据包中的加密消息
73
+ @param xmltext: 待提取的xml字符串
74
+ @return: 提取出的加密消息字符串
75
+ """
76
+ try:
77
+ xml_tree = ET.fromstring(xmltext)
78
+ encrypt = xml_tree.find("Encrypt")
79
+ return ierror.WXBizMsgCrypt_OK, encrypt.text
80
+ except Exception as e:
81
+ logger = logging.getLogger()
82
+ logger.error(e)
83
+ return ierror.WXBizMsgCrypt_ParseXml_Error, None
84
+
85
+ def generate(self, encrypt, signature, timestamp, nonce):
86
+ """生成xml消息
87
+ @param encrypt: 加密后的消息密文
88
+ @param signature: 安全签名
89
+ @param timestamp: 时间戳
90
+ @param nonce: 随机字符串
91
+ @return: 生成的xml字符串
92
+ """
93
+ resp_dict = {
94
+ 'msg_encrypt': encrypt,
95
+ 'msg_signaturet': signature,
96
+ 'timestamp': timestamp,
97
+ 'nonce': nonce,
98
+ }
99
+ resp_xml = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict
100
+ return resp_xml
101
+
102
+
103
+ class PKCS7Encoder():
104
+ """提供基于PKCS7算法的加解密接口"""
105
+
106
+ block_size = 32
107
+
108
+ def encode(self, text):
109
+ """ 对需要加密的明文进行填充补位
110
+ @param text: 需要进行填充补位操作的明文
111
+ @return: 补齐明文字符串
112
+ """
113
+ text_length = len(text)
114
+ # 计算需要填充的位数
115
+ amount_to_pad = self.block_size - (text_length % self.block_size)
116
+ if amount_to_pad == 0:
117
+ amount_to_pad = self.block_size
118
+ # 获得补位所用的字符
119
+ pad = chr(amount_to_pad)
120
+ return text + (pad * amount_to_pad).encode()
121
+
122
+ def decode(self, decrypted):
123
+ """删除解密后明文的补位字符
124
+ @param decrypted: 解密后的明文
125
+ @return: 删除补位字符后的明文
126
+ """
127
+ pad = ord(decrypted[-1])
128
+ if pad < 1 or pad > 32:
129
+ pad = 0
130
+ return decrypted[:-pad]
131
+
132
+
133
+ class Prpcrypt(object):
134
+ """提供接收和推送给企业微信消息的加解密接口"""
135
+
136
+ def __init__(self, key):
137
+
138
+ # self.key = base64.b64decode(key+"=")
139
+ self.key = key
140
+ # 设置加解密模式为AES的CBC模式
141
+ self.mode = AES.MODE_CBC
142
+
143
+ def encrypt(self, text, receiveid):
144
+ """对明文进行加密
145
+ @param text: 需要加密的明文
146
+ @return: 加密得到的字符串
147
+ """
148
+ # 16位随机字符串添加到明文开头
149
+ text = text.encode()
150
+ text = self.get_random_str() + struct.pack("I", socket.htonl(len(text))) + \
151
+ text + receiveid.encode()
152
+
153
+ # 使用自定义的填充方式对明文进行补位填充
154
+ pkcs7 = PKCS7Encoder()
155
+ text = pkcs7.encode(text)
156
+ # 加密
157
+ cryptor = AES.new(self.key, self.mode, self.key[:16])
158
+ try:
159
+ ciphertext = cryptor.encrypt(text)
160
+ # 使用BASE64对加密后的字符串进行编码
161
+ return ierror.WXBizMsgCrypt_OK, base64.b64encode(ciphertext)
162
+ except Exception as e:
163
+ logger = logging.getLogger()
164
+ logger.error(e)
165
+ return ierror.WXBizMsgCrypt_EncryptAES_Error, None
166
+
167
+ def decrypt(self, text, receiveid):
168
+ """对解密后的明文进行补位删除
169
+ @param text: 密文
170
+ @return: 删除填充补位后的明文
171
+ """
172
+ try:
173
+ cryptor = AES.new(self.key, self.mode, self.key[:16])
174
+ # 使用BASE64对密文进行解码,然后AES-CBC解密
175
+ plain_text = cryptor.decrypt(base64.b64decode(text))
176
+ except Exception as e:
177
+ logger = logging.getLogger()
178
+ logger.error(e)
179
+ return ierror.WXBizMsgCrypt_DecryptAES_Error, None
180
+ try:
181
+ pad = plain_text[-1]
182
+ # 去掉补位字符串
183
+ # pkcs7 = PKCS7Encoder()
184
+ # plain_text = pkcs7.encode(plain_text)
185
+ # 去除16位随机字符串
186
+ content = plain_text[16:-pad]
187
+ xml_len = socket.ntohl(struct.unpack("I", content[: 4])[0])
188
+ xml_content = content[4: xml_len + 4]
189
+ from_receiveid = content[xml_len + 4:]
190
+ except Exception as e:
191
+ logger = logging.getLogger()
192
+ logger.error(e)
193
+ return ierror.WXBizMsgCrypt_IllegalBuffer, None
194
+
195
+ if from_receiveid.decode('utf8') != receiveid:
196
+ return ierror.WXBizMsgCrypt_ValidateCorpid_Error, None
197
+ return 0, xml_content
198
+
199
+ def get_random_str(self):
200
+ """ 随机生成16位字符串
201
+ @return: 16位字符串
202
+ """
203
+ return str(random.randint(1000000000000000, 9999999999999999)).encode()
204
+
205
+
206
+ class WXBizMsgCrypt(object):
207
+ # 构造函数
208
+ def __init__(self, sToken, sEncodingAESKey, sReceiveId):
209
+ try:
210
+ self.key = base64.b64decode(sEncodingAESKey + "=")
211
+ assert len(self.key) == 32
212
+ except Exception:
213
+ throw_exception(
214
+ "[error]: EncodingAESKey unvalid !", FormatException)
215
+ # return ierror.WXBizMsgCrypt_IllegalAesKey,None
216
+ self.m_sToken = sToken
217
+ self.m_sReceiveId = sReceiveId
218
+
219
+ # 验证URL
220
+ # @param sMsgSignature: 签名串,对应URL参数的msg_signature
221
+ # @param sTimeStamp: 时间戳,对应URL参数的timestamp
222
+ # @param sNonce: 随机串,对应URL参数的nonce
223
+ # @param sEchoStr: 随机串,对应URL参数的echostr
224
+ # @param sReplyEchoStr: 解密之后的echostr,当return返回0时有效
225
+ # @return:成功0,失败返回对应的错误码
226
+
227
+ def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr):
228
+ sha1 = SHA1()
229
+ ret, signature = sha1.getSHA1(
230
+ self.m_sToken, sTimeStamp, sNonce, sEchoStr)
231
+ if ret != 0:
232
+ return ret, None
233
+ if not signature == sMsgSignature:
234
+ return ierror.WXBizMsgCrypt_ValidateSignature_Error, None
235
+ pc = Prpcrypt(self.key)
236
+ ret, sReplyEchoStr = pc.decrypt(sEchoStr, self.m_sReceiveId)
237
+ return ret, sReplyEchoStr
238
+
239
+ def EncryptMsg(self, sReplyMsg, sNonce, timestamp=None):
240
+ # 将企业回复用户的消息加密打包
241
+ # @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
242
+ # @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间
243
+ # @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
244
+ # sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
245
+ # return:成功0,sEncryptMsg,失败返回对应的错误码None
246
+ pc = Prpcrypt(self.key)
247
+ ret, encrypt = pc.encrypt(sReplyMsg, self.m_sReceiveId)
248
+ encrypt = encrypt.decode('utf8')
249
+ if ret != 0:
250
+ return ret, None
251
+ if timestamp is None:
252
+ timestamp = str(int(time.time()))
253
+ # 生成安全签名
254
+ sha1 = SHA1()
255
+ ret, signature = sha1.getSHA1(
256
+ self.m_sToken, timestamp, sNonce, encrypt)
257
+ if ret != 0:
258
+ return ret, None
259
+ xmlParse = XMLParse()
260
+ return ret, xmlParse.generate(encrypt, signature, timestamp, sNonce)
261
+
262
+ def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce):
263
+ # 检验消息的真实性,并且获取解密后的明文
264
+ # @param sMsgSignature: 签名串,对应URL参数的msg_signature
265
+ # @param sTimeStamp: 时间戳,对应URL参数的timestamp
266
+ # @param sNonce: 随机串,对应URL参数的nonce
267
+ # @param sPostData: 密文,对应POST请求的数据
268
+ # xml_content: 解密后的原文,当return返回0时有效
269
+ # @return: 成功0,失败返回对应的错误码
270
+ # 验证安全签名
271
+ xmlParse = XMLParse()
272
+ ret, encrypt = xmlParse.extract(sPostData)
273
+ if ret != 0:
274
+ return ret, None
275
+ sha1 = SHA1()
276
+ ret, signature = sha1.getSHA1(
277
+ self.m_sToken, sTimeStamp, sNonce, encrypt)
278
+ if ret != 0:
279
+ return ret, None
280
+ if not signature == sMsgSignature:
281
+ return ierror.WXBizMsgCrypt_ValidateSignature_Error, None
282
+ pc = Prpcrypt(self.key)
283
+ ret, xml_content = pc.decrypt(encrypt, self.m_sReceiveId)
284
+ return ret, xml_content
wechat-server/__init__.py ADDED
File without changes
wechat-server/ierror.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # !/usr/bin/env python
3
+ # -*- coding: utf-8 -*-
4
+ #########################################################################
5
+ # Author: jonyqin
6
+ # Created Time: Thu 11 Sep 2014 01:53:58 PM CST
7
+ # File Name: ierror.py
8
+ # Description:定义错误码含义
9
+ #########################################################################
10
+ WXBizMsgCrypt_OK = 0
11
+ WXBizMsgCrypt_ValidateSignature_Error = -40001
12
+ WXBizMsgCrypt_ParseXml_Error = -40002
13
+ WXBizMsgCrypt_ComputeSignature_Error = -40003
14
+ WXBizMsgCrypt_IllegalAesKey = -40004
15
+ WXBizMsgCrypt_ValidateCorpid_Error = -40005
16
+ WXBizMsgCrypt_EncryptAES_Error = -40006
17
+ WXBizMsgCrypt_DecryptAES_Error = -40007
18
+ WXBizMsgCrypt_IllegalBuffer = -40008
19
+ WXBizMsgCrypt_EncodeBase64_Error = -40009
20
+ WXBizMsgCrypt_DecodeBase64_Error = -40010
21
+ WXBizMsgCrypt_GenReturnXml_Error = -40011
wechat-server/web.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ import json
4
+ import os
5
+ from functools import lru_cache
6
+ from xml.etree.ElementTree import fromstring
7
+
8
+ import requests
9
+ import uvicorn
10
+ from fastapi import FastAPI, Request, Response
11
+ from WXBizMsgCrypt3 import WXBizMsgCrypt
12
+
13
+ from edu_assistant.learning_tasks import QaTask
14
+
15
+ instruction = """
16
+ Act as a c++ professional to answer student aged 5-10 questions. Answer properly and politely.
17
+ """
18
+ task = QaTask(instruction=instruction)
19
+
20
+ app = FastAPI()
21
+
22
+ TEXT_RESPONSE_TEMPLATE = """
23
+ <xml>
24
+ <ToUserName>{to_username}</ToUserName>
25
+ <FromUserName>{from_username}</FromUserName>
26
+ <CreateTime>{create_time}</CreateTime>
27
+ <MsgType>text</MsgType>
28
+ <Content>{content}</Content>
29
+ </xml>
30
+ """
31
+
32
+ WECHAT_TOKEN = os.environ.get("WECHAT_TOKEN")
33
+ WECHAT_AESKEY = os.environ.get("WECHAT_AESKEY")
34
+ WECHAT_CORPID = os.environ.get("WECHAT_CORPID")
35
+ WECHAT_SECRET = os.environ.get("WECHAT_SECRET")
36
+ CODEDOG_PORT = int(os.environ.get("CODEDOG_PORT", 32167))
37
+ wxcpt = WXBizMsgCrypt(WECHAT_TOKEN, WECHAT_AESKEY, WECHAT_CORPID)
38
+
39
+
40
+ def access_token():
41
+ url_base = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"
42
+ resp = requests.get(url_base % (WECHAT_CORPID, WECHAT_SECRET)).json()
43
+ return resp.get('access_token', '')
44
+
45
+
46
+ def get_chat_history(access_token: str, message_token: str):
47
+ url_base = "https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=" + access_token
48
+ body = {"token": message_token}
49
+ resp = requests.post(url_base, json=body)
50
+ """
51
+ [{
52
+ "msgid": "",
53
+ "open_kfid": "",
54
+ "external_userid": "",
55
+ "send_time": 1691854816,
56
+ "origin": 3,
57
+ "msgtype": "text",
58
+ "text": {
59
+ "content": "哈哈哈哈哈啊"
60
+ }
61
+ }]
62
+ """
63
+ # print("history", json.dumps(resp.json(), indent=2, ensure_ascii=False))
64
+ return resp.json()
65
+
66
+
67
+ def reply(external_user_id, open_kfid, content):
68
+ url_base = "https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?debug=1&access_token=" + access_token()
69
+ body = {
70
+ "touser": external_user_id,
71
+ "open_kfid": open_kfid,
72
+ "msgtype": "text",
73
+ "text": {
74
+ "content": content
75
+ }
76
+ }
77
+ resp = requests.post(url_base, json=body).json()
78
+ print(json.dumps(resp, indent=2, ensure_ascii=False))
79
+ return resp.get("errcode", 1) == 0
80
+
81
+
82
+ # 企业微信同一条用户的消息会发三次回调,内容都一样
83
+ # 这里加个 lru_cache 挡一挡,纯属偷懒
84
+ @lru_cache()
85
+ def handle_message(message_token: str):
86
+
87
+ history = get_chat_history(access_token=access_token(),
88
+ message_token=message_token)["msg_list"]
89
+
90
+ message_block = history[-1]
91
+ open_kfid = message_block["open_kfid"]
92
+ external_userid = message_block["external_userid"]
93
+ content = message_block["text"]["content"]
94
+ # print(open_kfid, external_userid, content)
95
+ result = task.ask(content, session=False)
96
+ """
97
+ {
98
+ "input": "",
99
+ "chat_history": "",
100
+ "text": ""
101
+ }
102
+ """
103
+ reply(external_userid, open_kfid, result["text"])
104
+
105
+
106
+ @app.get("/")
107
+ async def verify(msg_signature: str,
108
+ timestamp: str,
109
+ nonce: str,
110
+ echostr: str):
111
+ '''
112
+ 验证配置是否成功,处理get请求
113
+ :param msg_signature:
114
+ :param timestamp:
115
+ :param nonce:
116
+ :param echostr:
117
+ :return:
118
+ '''
119
+ ret, sEchoStr = wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr)
120
+ if ret == 0:
121
+ return Response(content=sEchoStr.decode('utf-8'))
122
+ else:
123
+ print(sEchoStr)
124
+
125
+
126
+ @app.post("/")
127
+ async def recv(msg_signature: str,
128
+ timestamp: str,
129
+ nonce: str,
130
+ request: Request):
131
+ '''
132
+ 接收用户消息,可进行被动响应
133
+ :param msg_signature:
134
+ :param timestamp:
135
+ :param nonce:
136
+ :param request:
137
+ :return:
138
+ '''
139
+ body = await request.body()
140
+ ret, msg = wxcpt.DecryptMsg(body.decode(
141
+ 'utf-8'), msg_signature, timestamp, nonce)
142
+ decrypt_data = {}
143
+ for node in list(fromstring(msg.decode('utf-8'))):
144
+ decrypt_data[node.tag] = node.text
145
+ # 解析后得到的decrypt_data:
146
+ # {"ToUserName":"企业号", "FromUserName":"发送者用户名", "CreateTime":"发送时间",
147
+ # "Content":"用户发送的内容", "MsgId":"唯一id,需要针对此id做出响应", "AagentID": "应用id"}
148
+ print("decrypt_data", decrypt_data)
149
+
150
+ message_token = decrypt_data["Token"]
151
+ # 真正进行回复的地方
152
+ handle_message(message_token)
153
+
154
+ # 这里只需要响应一下回调函数即可,不需要返回数据
155
+ return Response(content="success")
156
+
157
+ # resp_data = TEXT_RESPONSE_TEMPLATE.format(to_username=decrypt_data.get("ToUserName", ""),
158
+ # from_username=decrypt_data.get(
159
+ # "FromUserName", ""),
160
+ # create_time=decrypt_data.get(
161
+ # "CreateTime", ""),
162
+ # content="帅得一逼",)
163
+ # ret, send_msg = wxcpt.EncryptMsg(sReplyMsg=resp_data, sNonce=nonce)
164
+ # if ret == 0:
165
+ # return Response(content=send_msg)
166
+ # else:
167
+ # print(send_msg)
168
+
169
+
170
+ if __name__ == "__main__":
171
+ uvicorn.run("web:app", port=CODEDOG_PORT, host='0.0.0.0', reload=False)