Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

179 lignes
8.1KB

  1. # -*- coding=utf-8 -*-
  2. import io
  3. import os
  4. import textwrap
  5. import time
  6. import requests
  7. import web
  8. from wechatpy.enterprise import create_reply, parse_message
  9. from wechatpy.enterprise.crypto import WeChatCrypto
  10. from wechatpy.enterprise.exceptions import InvalidCorpIdException
  11. from wechatpy.exceptions import InvalidSignatureException, WeChatClientException
  12. from bridge.context import Context
  13. from bridge.reply import Reply, ReplyType
  14. from channel.chat_channel import ChatChannel
  15. from channel.wechatcom.wechatcomapp_client import WechatComAppClient
  16. from channel.wechatcom.wechatcomapp_message import WechatComAppMessage
  17. from common.log import logger
  18. from common.singleton import singleton
  19. from common.utils import compress_imgfile, fsize, split_string_by_utf8_length
  20. from config import conf
  21. from voice.audio_convert import any_to_amr
  22. MAX_UTF8_LEN = 2048
  23. @singleton
  24. class WechatComAppChannel(ChatChannel):
  25. NOT_SUPPORT_REPLYTYPE = []
  26. def __init__(self):
  27. super().__init__()
  28. self.corp_id = conf().get("wechatcom_corp_id")
  29. self.secret = conf().get("wechatcomapp_secret")
  30. self.agent_id = conf().get("wechatcomapp_agent_id")
  31. self.token = conf().get("wechatcomapp_token")
  32. self.aes_key = conf().get("wechatcomapp_aes_key")
  33. print(self.corp_id, self.secret, self.agent_id, self.token, self.aes_key)
  34. logger.info(
  35. "[wechatcom] init: corp_id: {}, secret: {}, agent_id: {}, token: {}, aes_key: {}".format(self.corp_id, self.secret, self.agent_id, self.token, self.aes_key)
  36. )
  37. self.crypto = WeChatCrypto(self.token, self.aes_key, self.corp_id)
  38. self.client = WechatComAppClient(self.corp_id, self.secret)
  39. def startup(self):
  40. # start message listener
  41. urls = ("/wxcomapp", "channel.wechatcom.wechatcomapp_channel.Query")
  42. app = web.application(urls, globals(), autoreload=False)
  43. port = conf().get("wechatcomapp_port", 9898)
  44. web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port))
  45. def send(self, reply: Reply, context: Context):
  46. receiver = context["receiver"]
  47. if reply.type in [ReplyType.TEXT, ReplyType.ERROR, ReplyType.INFO]:
  48. reply_text = reply.content
  49. texts = split_string_by_utf8_length(reply_text, MAX_UTF8_LEN)
  50. if len(texts) > 1:
  51. logger.info("[wechatcom] text too long, split into {} parts".format(len(texts)))
  52. for i, text in enumerate(texts):
  53. self.client.message.send_text(self.agent_id, receiver, text)
  54. if i != len(texts) - 1:
  55. time.sleep(0.5) # 休眠0.5秒,防止发送过快乱序
  56. logger.info("[wechatcom] Do send text to {}: {}".format(receiver, reply_text))
  57. elif reply.type == ReplyType.VOICE:
  58. try:
  59. file_path = reply.content
  60. amr_file = os.path.splitext(file_path)[0] + ".amr"
  61. any_to_amr(file_path, amr_file)
  62. response = self.client.media.upload("voice", open(amr_file, "rb"))
  63. logger.debug("[wechatcom] upload voice response: {}".format(response))
  64. except WeChatClientException as e:
  65. logger.error("[wechatcom] upload voice failed: {}".format(e))
  66. return
  67. try:
  68. os.remove(file_path)
  69. if amr_file != file_path:
  70. os.remove(amr_file)
  71. except Exception:
  72. pass
  73. self.client.message.send_voice(self.agent_id, receiver, response["media_id"])
  74. logger.info("[wechatcom] sendVoice={}, receiver={}".format(reply.content, receiver))
  75. elif reply.type == ReplyType.IMAGE_URL: # 从网络下载图片
  76. img_url = reply.content
  77. pic_res = requests.get(img_url, stream=True)
  78. image_storage = io.BytesIO()
  79. for block in pic_res.iter_content(1024):
  80. image_storage.write(block)
  81. if (sz := fsize(image_storage)) >= 10 * 1024 * 1024:
  82. logger.info("[wechatcom] image too large, ready to compress, sz={}".format(sz))
  83. image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
  84. logger.info("[wechatcom] image compressed, sz={}".format(fsize(image_storage)))
  85. image_storage.seek(0)
  86. try:
  87. response = self.client.media.upload("image", image_storage)
  88. logger.debug("[wechatcom] upload image response: {}".format(response))
  89. except WeChatClientException as e:
  90. logger.error("[wechatcom] upload image failed: {}".format(e))
  91. return
  92. self.client.message.send_image(self.agent_id, receiver, response["media_id"])
  93. logger.info("[wechatcom] sendImage url={}, receiver={}".format(img_url, receiver))
  94. elif reply.type == ReplyType.IMAGE: # 从文件读取图片
  95. image_storage = reply.content
  96. if (sz := fsize(image_storage)) >= 10 * 1024 * 1024:
  97. logger.info("[wechatcom] image too large, ready to compress, sz={}".format(sz))
  98. image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
  99. logger.info("[wechatcom] image compressed, sz={}".format(fsize(image_storage)))
  100. image_storage.seek(0)
  101. try:
  102. response = self.client.media.upload("image", image_storage)
  103. logger.debug("[wechatcom] upload image response: {}".format(response))
  104. except WeChatClientException as e:
  105. logger.error("[wechatcom] upload image failed: {}".format(e))
  106. return
  107. self.client.message.send_image(self.agent_id, receiver, response["media_id"])
  108. logger.info("[wechatcom] sendImage, receiver={}".format(receiver))
  109. class Query:
  110. def GET(self):
  111. channel = WechatComAppChannel()
  112. params = web.input()
  113. logger.info("[wechatcom] receive params: {}".format(params))
  114. try:
  115. signature = params.msg_signature
  116. timestamp = params.timestamp
  117. nonce = params.nonce
  118. echostr = params.echostr
  119. echostr = channel.crypto.check_signature(signature, timestamp, nonce, echostr)
  120. except InvalidSignatureException:
  121. raise web.Forbidden()
  122. return echostr
  123. def POST(self):
  124. channel = WechatComAppChannel()
  125. params = web.input()
  126. logger.info("[wechatcom] receive params: {}".format(params))
  127. try:
  128. signature = params.msg_signature
  129. timestamp = params.timestamp
  130. nonce = params.nonce
  131. message = channel.crypto.decrypt_message(web.data(), signature, timestamp, nonce)
  132. except (InvalidSignatureException, InvalidCorpIdException):
  133. raise web.Forbidden()
  134. msg = parse_message(message)
  135. logger.debug("[wechatcom] receive message: {}, msg= {}".format(message, msg))
  136. if msg.type == "event":
  137. if msg.event == "subscribe":
  138. trigger_prefix = conf().get("single_chat_prefix", [""])[0]
  139. reply_content = textwrap.dedent(
  140. f"""\
  141. 感谢您的关注!
  142. 这里是ChatGPT,可以自由对话。
  143. 支持语音对话。
  144. 支持通用表情输入。
  145. 支持图片输入输出。
  146. 支持角色扮演和文字冒险两种定制模式对话。
  147. 输入'{trigger_prefix}#help' 查看详细指令。"""
  148. )
  149. reply = create_reply(reply_content, msg).render()
  150. res = channel.crypto.encrypt_message(reply, nonce, timestamp)
  151. return res
  152. else:
  153. try:
  154. wechatcom_msg = WechatComAppMessage(msg, client=channel.client)
  155. except NotImplementedError as e:
  156. logger.debug("[wechatcom] " + str(e))
  157. return "success"
  158. context = channel._compose_context(
  159. wechatcom_msg.ctype,
  160. wechatcom_msg.content,
  161. isgroup=False,
  162. msg=wechatcom_msg,
  163. )
  164. if context:
  165. channel.produce(context)
  166. return "success"