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.

194 lines
8.4KB

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