您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

197 行
8.5KB

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