選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

231 行
10KB

  1. import time
  2. import asyncio
  3. import web
  4. from channel.wechatmp.wechatmp_message import WeChatMPMessage
  5. from bridge.context import *
  6. from bridge.reply import *
  7. from channel.wechatmp.common import *
  8. from channel.wechatmp.wechatmp_channel import WechatMPChannel
  9. from common.log import logger
  10. from config import conf
  11. from wechatpy import parse_message
  12. from wechatpy.replies import create_reply, ImageReply, VoiceReply
  13. # This class is instantiated once per query
  14. class Query:
  15. def GET(self):
  16. return verify_server(web.input())
  17. def POST(self):
  18. try:
  19. args = web.input()
  20. verify_server(args)
  21. request_time = time.time()
  22. channel = WechatMPChannel()
  23. message = web.data()
  24. encrypt_func = lambda x: x
  25. if args.get("encrypt_type") == "aes":
  26. logger.debug("[wechatmp] Receive encrypted post data:\n" + message.decode("utf-8"))
  27. if not channel.crypto:
  28. raise Exception("Crypto not initialized, Please set wechatmp_aes_key in config.json")
  29. message = channel.crypto.decrypt_message(message, args.msg_signature, args.timestamp, args.nonce)
  30. encrypt_func = lambda x: channel.crypto.encrypt_message(x, args.nonce, args.timestamp)
  31. else:
  32. logger.debug("[wechatmp] Receive post data:\n" + message.decode("utf-8"))
  33. msg = parse_message(message)
  34. if msg.type in ["text", "voice", "image"]:
  35. wechatmp_msg = WeChatMPMessage(msg, client=channel.client)
  36. from_user = wechatmp_msg.from_user_id
  37. content = wechatmp_msg.content
  38. message_id = wechatmp_msg.msg_id
  39. supported = True
  40. if "【收到不支持的消息类型,暂无法显示】" in content:
  41. supported = False # not supported, used to refresh
  42. # New request
  43. if (
  44. from_user not in channel.cache_dict
  45. and from_user not in channel.running
  46. or content.startswith("#")
  47. and message_id not in channel.request_cnt # insert the godcmd
  48. ):
  49. # The first query begin
  50. if msg.type == "voice" and wechatmp_msg.ctype == ContextType.TEXT and conf().get("voice_reply_voice", False):
  51. context = channel._compose_context(
  52. wechatmp_msg.ctype, content, isgroup=False, desire_rtype=ReplyType.VOICE, msg=wechatmp_msg
  53. )
  54. else:
  55. context = channel._compose_context(
  56. wechatmp_msg.ctype, content, isgroup=False, msg=wechatmp_msg
  57. )
  58. logger.debug(
  59. "[wechatmp] context: {} {} {}".format(context, wechatmp_msg, supported)
  60. )
  61. if supported and context:
  62. # set private openai_api_key
  63. # if from_user is not changed in itchat, this can be placed at chat_channel
  64. user_data = conf().get_user_data(from_user)
  65. context["openai_api_key"] = user_data.get("openai_api_key")
  66. channel.running.add(from_user)
  67. channel.produce(context)
  68. else:
  69. trigger_prefix = conf().get("single_chat_prefix", [""])[0]
  70. if trigger_prefix or not supported:
  71. if trigger_prefix:
  72. reply_text = textwrap.dedent(
  73. f"""\
  74. 请输入'{trigger_prefix}'接你想说的话跟我说话。
  75. 例如:
  76. {trigger_prefix}你好,很高兴见到你。"""
  77. )
  78. else:
  79. reply_text = textwrap.dedent(
  80. """\
  81. 你好,很高兴见到你。
  82. 请跟我说话吧。"""
  83. )
  84. else:
  85. logger.error(f"[wechatmp] unknown error")
  86. reply_text = textwrap.dedent(
  87. """\
  88. 未知错误,请稍后再试"""
  89. )
  90. replyPost = create_reply(reply_text, msg)
  91. return encrypt_func(replyPost.render())
  92. # Wechat official server will request 3 times (5 seconds each), with the same message_id.
  93. # Because the interval is 5 seconds, here assumed that do not have multithreading problems.
  94. request_cnt = channel.request_cnt.get(message_id, 0) + 1
  95. channel.request_cnt[message_id] = request_cnt
  96. logger.info(
  97. "[wechatmp] Request {} from {} {} {}:{}\n{}".format(
  98. request_cnt,
  99. from_user,
  100. message_id,
  101. web.ctx.env.get("REMOTE_ADDR"),
  102. web.ctx.env.get("REMOTE_PORT"),
  103. content
  104. )
  105. )
  106. task_running = True
  107. waiting_until = request_time + 4
  108. while time.time() < waiting_until:
  109. if from_user in channel.running:
  110. time.sleep(0.1)
  111. else:
  112. task_running = False
  113. break
  114. reply_text = ""
  115. if task_running:
  116. if request_cnt < 3:
  117. # waiting for timeout (the POST request will be closed by Wechat official server)
  118. time.sleep(2)
  119. # and do nothing, waiting for the next request
  120. return "success"
  121. else: # request_cnt == 3:
  122. # return timeout message
  123. reply_text = "【正在思考中,回复任意文字尝试获取回复】"
  124. replyPost = create_reply(reply_text, msg)
  125. return encrypt_func(replyPost.render())
  126. # reply is ready
  127. channel.request_cnt.pop(message_id)
  128. # no return because of bandwords or other reasons
  129. if (
  130. from_user not in channel.cache_dict
  131. and from_user not in channel.running
  132. ):
  133. return "success"
  134. # Only one request can access to the cached data
  135. try:
  136. (reply_type, reply_content) = channel.cache_dict.pop(from_user)
  137. except KeyError:
  138. return "success"
  139. if (reply_type == "text"):
  140. if len(reply_content.encode("utf8")) <= MAX_UTF8_LEN:
  141. reply_text = reply_content
  142. else:
  143. continue_text = "\n【未完待续,回复任意文字以继续】"
  144. splits = split_string_by_utf8_length(
  145. reply_content,
  146. MAX_UTF8_LEN - len(continue_text.encode("utf-8")),
  147. max_split=1,
  148. )
  149. reply_text = splits[0] + continue_text
  150. channel.cache_dict[from_user] = ("text", splits[1])
  151. logger.info(
  152. "[wechatmp] Request {} do send to {} {}: {}\n{}".format(
  153. request_cnt,
  154. from_user,
  155. message_id,
  156. content,
  157. reply_text,
  158. )
  159. )
  160. replyPost = create_reply(reply_text, msg)
  161. return encrypt_func(replyPost.render())
  162. elif (reply_type == "voice"):
  163. media_id = reply_content
  164. asyncio.run_coroutine_threadsafe(channel.delete_media(media_id), channel.delete_media_loop)
  165. logger.info(
  166. "[wechatmp] Request {} do send to {} {}: {} voice media_id {}".format(
  167. request_cnt,
  168. from_user,
  169. message_id,
  170. content,
  171. media_id,
  172. )
  173. )
  174. replyPost = VoiceReply(message=msg)
  175. replyPost.media_id = media_id
  176. return encrypt_func(replyPost.render())
  177. elif (reply_type == "image"):
  178. media_id = reply_content
  179. asyncio.run_coroutine_threadsafe(channel.delete_media(media_id), channel.delete_media_loop)
  180. logger.info(
  181. "[wechatmp] Request {} do send to {} {}: {} image media_id {}".format(
  182. request_cnt,
  183. from_user,
  184. message_id,
  185. content,
  186. media_id,
  187. )
  188. )
  189. replyPost = ImageReply(message=msg)
  190. replyPost.media_id = media_id
  191. return encrypt_func(replyPost.render())
  192. elif msg.type == "event":
  193. logger.info(
  194. "[wechatmp] Event {} from {}".format(
  195. msg.event, msg.source
  196. )
  197. )
  198. if msg.event in ["subscribe", "subscribe_scan"]:
  199. reply_text = subscribe_msg()
  200. replyPost = create_reply(reply_text, msg)
  201. return encrypt_func(replyPost.render())
  202. else:
  203. return "success"
  204. else:
  205. logger.info("暂且不处理")
  206. return "success"
  207. except Exception as exc:
  208. logger.exception(exc)
  209. return exc