You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

223 lines
9.5KB

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