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.

169 lignes
7.7KB

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