Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

203 lines
8.8KB

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