Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

214 lines
9.9KB

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