Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

234 lines
10KB

  1. # -*- coding: utf-8 -*-
  2. import web
  3. import time
  4. import math
  5. import hashlib
  6. import textwrap
  7. from channel.chat_channel import ChatChannel
  8. import channel.wechatmp.reply as reply
  9. import channel.wechatmp.receive as receive
  10. from common.singleton import singleton
  11. from common.log import logger
  12. from config import conf
  13. from bridge.reply import *
  14. from bridge.context import *
  15. from plugins import *
  16. import traceback
  17. # If using SSL, uncomment the following lines, and modify the certificate path.
  18. # from cheroot.server import HTTPServer
  19. # from cheroot.ssl.builtin import BuiltinSSLAdapter
  20. # HTTPServer.ssl_adapter = BuiltinSSLAdapter(
  21. # certificate='/ssl/cert.pem',
  22. # private_key='/ssl/cert.key')
  23. # from concurrent.futures import ThreadPoolExecutor
  24. # thread_pool = ThreadPoolExecutor(max_workers=8)
  25. @singleton
  26. class WechatMPChannel(ChatChannel):
  27. def __init__(self):
  28. super().__init__()
  29. self.cache_dict = dict()
  30. self.query1 = dict()
  31. self.query2 = dict()
  32. self.query3 = dict()
  33. def startup(self):
  34. urls = (
  35. '/wx', 'SubsribeAccountQuery',
  36. )
  37. app = web.application(urls, globals())
  38. port = conf().get('wechatmp_port', 8080)
  39. web.httpserver.runsimple(app.wsgifunc(), ('0.0.0.0', port))
  40. def send(self, reply: Reply, context: Context):
  41. reply_cnt = math.ceil(len(reply.content) / 600)
  42. receiver = context["receiver"]
  43. self.cache_dict[receiver] = (reply_cnt, reply.content)
  44. logger.debug("[send] reply to {} saved to cache: {}".format(receiver, reply))
  45. def verify_server():
  46. try:
  47. data = web.input()
  48. if len(data) == 0:
  49. return "None"
  50. signature = data.signature
  51. timestamp = data.timestamp
  52. nonce = data.nonce
  53. echostr = data.echostr
  54. token = conf().get('wechatmp_token') #请按照公众平台官网\基本配置中信息填写
  55. data_list = [token, timestamp, nonce]
  56. data_list.sort()
  57. sha1 = hashlib.sha1()
  58. # map(sha1.update, data_list) #python2
  59. sha1.update("".join(data_list).encode('utf-8'))
  60. hashcode = sha1.hexdigest()
  61. print("handle/GET func: hashcode, signature: ", hashcode, signature)
  62. if hashcode == signature:
  63. return echostr
  64. else:
  65. return ""
  66. except Exception as Argument:
  67. return Argument
  68. # This class is instantiated once per query
  69. class SubsribeAccountQuery():
  70. def GET(self):
  71. return verify_server()
  72. def POST(self):
  73. channel_instance = WechatMPChannel()
  74. try:
  75. query_time = time.time()
  76. webData = web.data()
  77. # logger.debug("[wechatmp] Receive request:\n" + webData.decode("utf-8"))
  78. wechat_msg = receive.parse_xml(webData)
  79. if wechat_msg.msg_type == 'text':
  80. from_user = wechat_msg.from_user_id
  81. to_user = wechat_msg.to_user_id
  82. message = wechat_msg.content.decode("utf-8")
  83. message_id = wechat_msg.msg_id
  84. logger.info("[wechatmp] {}:{} Receive post query {} {}: {}".format(web.ctx.env.get('REMOTE_ADDR'), web.ctx.env.get('REMOTE_PORT'), from_user, message_id, message))
  85. cache_key = from_user
  86. cache = channel_instance.cache_dict.get(cache_key)
  87. reply_text = ""
  88. # New request
  89. if cache == None:
  90. # The first query begin, reset the cache
  91. context = channel_instance._compose_context(ContextType.TEXT, message, isgroup=False, msg=wechat_msg)
  92. logger.debug("[wechatmp] context: {} {}".format(context, wechat_msg))
  93. if context:
  94. # set private openai_api_key
  95. # if from_user is not changed in itchat, this can be placed at chat_channel
  96. user_data = conf().get_user_data(from_user)
  97. context['openai_api_key'] = user_data.get('openai_api_key') # None or user openai_api_key
  98. channel_instance.cache_dict[cache_key] = (0, "")
  99. channel_instance.produce(context)
  100. else:
  101. trigger_prefix = conf().get('single_chat_prefix',[''])[0]
  102. if trigger_prefix:
  103. content = textwrap.dedent(f"""\
  104. 请输入'{trigger_prefix}'接你想说的话跟我说话。
  105. 例如:
  106. {trigger_prefix}你好,很高兴见到你。""")
  107. else:
  108. logger.error(f"[wechatmp] unknown error")
  109. content = textwrap.dedent("""\
  110. 未知错误,请稍后再试""")
  111. replyMsg = reply.TextMsg(wechat_msg.from_user_id, wechat_msg.to_user_id, content)
  112. return replyMsg.send()
  113. channel_instance.query1[cache_key] = False
  114. channel_instance.query2[cache_key] = False
  115. channel_instance.query3[cache_key] = False
  116. # Request again
  117. elif cache[0] == 0 and channel_instance.query1.get(cache_key) == True and channel_instance.query2.get(cache_key) == True and channel_instance.query3.get(cache_key) == True:
  118. channel_instance.query1[cache_key] = False #To improve waiting experience, this can be set to True.
  119. channel_instance.query2[cache_key] = False #To improve waiting experience, this can be set to True.
  120. channel_instance.query3[cache_key] = False
  121. elif cache[0] >= 1:
  122. # Skip the waiting phase
  123. channel_instance.query1[cache_key] = True
  124. channel_instance.query2[cache_key] = True
  125. channel_instance.query3[cache_key] = True
  126. cache = channel_instance.cache_dict.get(cache_key)
  127. if channel_instance.query1.get(cache_key) == False:
  128. # The first query from wechat official server
  129. logger.debug("[wechatmp] query1 {}".format(cache_key))
  130. channel_instance.query1[cache_key] = True
  131. cnt = 0
  132. while cache[0] == 0 and cnt < 45:
  133. cnt = cnt + 1
  134. time.sleep(0.1)
  135. cache = channel_instance.cache_dict.get(cache_key)
  136. if cnt == 45:
  137. # waiting for timeout (the POST query will be closed by wechat official server)
  138. time.sleep(5)
  139. # and do nothing
  140. return
  141. else:
  142. pass
  143. elif channel_instance.query2.get(cache_key) == False:
  144. # The second query from wechat official server
  145. logger.debug("[wechatmp] query2 {}".format(cache_key))
  146. channel_instance.query2[cache_key] = True
  147. cnt = 0
  148. while cache[0] == 0 and cnt < 45:
  149. cnt = cnt + 1
  150. time.sleep(0.1)
  151. cache = channel_instance.cache_dict.get(cache_key)
  152. if cnt == 45:
  153. # waiting for timeout (the POST query will be closed by wechat official server)
  154. time.sleep(5)
  155. # and do nothing
  156. return
  157. else:
  158. pass
  159. elif channel_instance.query3.get(cache_key) == False:
  160. # The third query from wechat official server
  161. logger.debug("[wechatmp] query3 {}".format(cache_key))
  162. channel_instance.query3[cache_key] = True
  163. cnt = 0
  164. while cache[0] == 0 and cnt < 45:
  165. cnt = cnt + 1
  166. time.sleep(0.1)
  167. cache = channel_instance.cache_dict.get(cache_key)
  168. if cnt == 45:
  169. # Have waiting for 3x5 seconds
  170. # return timeout message
  171. reply_text = "【正在响应中,回复任意文字尝试获取回复】"
  172. logger.info("[wechatmp] Three queries has finished For {}: {}".format(from_user, message_id))
  173. replyPost = reply.TextMsg(from_user, to_user, reply_text).send()
  174. return replyPost
  175. else:
  176. pass
  177. if float(time.time()) - float(query_time) > 4.8:
  178. logger.info("[wechatmp] Timeout for {} {}".format(from_user, message_id))
  179. return
  180. if cache[0] > 1:
  181. reply_text = cache[1][:600] + "\n【未完待续,回复任意文字以继续】" #wechatmp auto_reply length limit
  182. channel_instance.cache_dict[cache_key] = (cache[0] - 1, cache[1][600:])
  183. elif cache[0] == 1:
  184. reply_text = cache[1]
  185. channel_instance.cache_dict.pop(cache_key)
  186. logger.info("[wechatmp] {}:{} Do send {}".format(web.ctx.env.get('REMOTE_ADDR'), web.ctx.env.get('REMOTE_PORT'), reply_text))
  187. replyPost = reply.TextMsg(from_user, to_user, reply_text).send()
  188. return replyPost
  189. elif wechat_msg.msg_type == 'event':
  190. logger.info("[wechatmp] Event {} from {}".format(wechat_msg.Event, wechat_msg.from_user_id))
  191. trigger_prefix = conf().get('single_chat_prefix',[''])[0]
  192. content = textwrap.dedent(f"""\
  193. 感谢您的关注!
  194. 这里是ChatGPT,可以自由对话。
  195. 资源有限,回复较慢,请勿着急。
  196. 支持通用表情输入。
  197. 暂时不支持图片输入。
  198. 支持图片输出,画字开头的问题将回复图片链接。
  199. 支持角色扮演和文字冒险两种定制模式对话。
  200. 输入'{trigger_prefix}#帮助' 查看详细指令。""")
  201. replyMsg = reply.TextMsg(wechat_msg.from_user_id, wechat_msg.to_user_id, content)
  202. return replyMsg.send()
  203. else:
  204. logger.info("暂且不处理")
  205. return "success"
  206. except Exception as exc:
  207. logger.exception(exc)
  208. return exc