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.

185 line
8.4KB

  1. # -*- coding=utf-8 -*-
  2. import io
  3. import os
  4. import time
  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, split_string_by_utf8_length, convert_webp_to_png
  19. from config import conf, subscribe_msg
  20. from voice.audio_convert import any_to_amr, split_audio
  21. MAX_UTF8_LEN = 2048
  22. @singleton
  23. class WechatComAppChannel(ChatChannel):
  24. NOT_SUPPORT_REPLYTYPE = []
  25. def __init__(self):
  26. super().__init__()
  27. self.corp_id = conf().get("wechatcom_corp_id")
  28. self.secret = conf().get("wechatcomapp_secret")
  29. self.agent_id = conf().get("wechatcomapp_agent_id")
  30. self.token = conf().get("wechatcomapp_token")
  31. self.aes_key = conf().get("wechatcomapp_aes_key")
  32. print(self.corp_id, self.secret, self.agent_id, self.token, self.aes_key)
  33. logger.info(
  34. "[wechatcom] init: corp_id: {}, secret: {}, agent_id: {}, token: {}, aes_key: {}".format(self.corp_id, self.secret, self.agent_id, self.token, self.aes_key)
  35. )
  36. self.crypto = WeChatCrypto(self.token, self.aes_key, self.corp_id)
  37. self.client = WechatComAppClient(self.corp_id, self.secret)
  38. def startup(self):
  39. # start message listener
  40. urls = ("/wxcomapp/?", "channel.wechatcom.wechatcomapp_channel.Query")
  41. app = web.application(urls, globals(), autoreload=False)
  42. port = conf().get("wechatcomapp_port", 9898)
  43. web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port))
  44. def send(self, reply: Reply, context: Context):
  45. receiver = context["receiver"]
  46. if reply.type in [ReplyType.TEXT, ReplyType.ERROR, ReplyType.INFO]:
  47. reply_text = reply.content
  48. texts = split_string_by_utf8_length(reply_text, MAX_UTF8_LEN)
  49. if len(texts) > 1:
  50. logger.info("[wechatcom] text too long, split into {} parts".format(len(texts)))
  51. for i, text in enumerate(texts):
  52. self.client.message.send_text(self.agent_id, receiver, text)
  53. if i != len(texts) - 1:
  54. time.sleep(0.5) # 休眠0.5秒,防止发送过快乱序
  55. logger.info("[wechatcom] Do send text to {}: {}".format(receiver, reply_text))
  56. elif reply.type == ReplyType.VOICE:
  57. try:
  58. media_ids = []
  59. file_path = reply.content
  60. amr_file = os.path.splitext(file_path)[0] + ".amr"
  61. any_to_amr(file_path, amr_file)
  62. duration, files = split_audio(amr_file, 60 * 1000)
  63. if len(files) > 1:
  64. logger.info("[wechatcom] voice too long {}s > 60s , split into {} parts".format(duration / 1000.0, len(files)))
  65. for path in files:
  66. response = self.client.media.upload("voice", open(path, "rb"))
  67. logger.debug("[wechatcom] upload voice response: {}".format(response))
  68. media_ids.append(response["media_id"])
  69. except WeChatClientException as e:
  70. logger.error("[wechatcom] upload voice failed: {}".format(e))
  71. return
  72. try:
  73. os.remove(file_path)
  74. if amr_file != file_path:
  75. os.remove(amr_file)
  76. except Exception:
  77. pass
  78. for media_id in media_ids:
  79. self.client.message.send_voice(self.agent_id, receiver, media_id)
  80. time.sleep(1)
  81. logger.info("[wechatcom] sendVoice={}, receiver={}".format(reply.content, receiver))
  82. elif reply.type == ReplyType.IMAGE_URL: # 从网络下载图片
  83. img_url = reply.content
  84. pic_res = requests.get(img_url, stream=True)
  85. image_storage = io.BytesIO()
  86. for block in pic_res.iter_content(1024):
  87. image_storage.write(block)
  88. sz = fsize(image_storage)
  89. if sz >= 10 * 1024 * 1024:
  90. logger.info("[wechatcom] image too large, ready to compress, sz={}".format(sz))
  91. image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
  92. logger.info("[wechatcom] image compressed, sz={}".format(fsize(image_storage)))
  93. image_storage.seek(0)
  94. if ".webp" in img_url:
  95. try:
  96. image_storage = convert_webp_to_png(image_storage)
  97. except Exception as e:
  98. logger.error(f"Failed to convert image: {e}")
  99. return
  100. try:
  101. response = self.client.media.upload("image", image_storage)
  102. logger.debug("[wechatcom] upload image response: {}".format(response))
  103. except WeChatClientException as e:
  104. logger.error("[wechatcom] upload image failed: {}".format(e))
  105. return
  106. self.client.message.send_image(self.agent_id, receiver, response["media_id"])
  107. logger.info("[wechatcom] sendImage url={}, receiver={}".format(img_url, receiver))
  108. elif reply.type == ReplyType.IMAGE: # 从文件读取图片
  109. image_storage = reply.content
  110. sz = fsize(image_storage)
  111. if sz >= 10 * 1024 * 1024:
  112. logger.info("[wechatcom] image too large, ready to compress, sz={}".format(sz))
  113. image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
  114. logger.info("[wechatcom] image compressed, sz={}".format(fsize(image_storage)))
  115. image_storage.seek(0)
  116. try:
  117. response = self.client.media.upload("image", image_storage)
  118. logger.debug("[wechatcom] upload image response: {}".format(response))
  119. except WeChatClientException as e:
  120. logger.error("[wechatcom] upload image failed: {}".format(e))
  121. return
  122. self.client.message.send_image(self.agent_id, receiver, response["media_id"])
  123. logger.info("[wechatcom] sendImage, receiver={}".format(receiver))
  124. class Query:
  125. def GET(self):
  126. channel = WechatComAppChannel()
  127. params = web.input()
  128. logger.info("[wechatcom] receive params: {}".format(params))
  129. try:
  130. signature = params.msg_signature
  131. timestamp = params.timestamp
  132. nonce = params.nonce
  133. echostr = params.echostr
  134. echostr = channel.crypto.check_signature(signature, timestamp, nonce, echostr)
  135. except InvalidSignatureException:
  136. raise web.Forbidden()
  137. return echostr
  138. def POST(self):
  139. channel = WechatComAppChannel()
  140. params = web.input()
  141. logger.info("[wechatcom] receive params: {}".format(params))
  142. try:
  143. signature = params.msg_signature
  144. timestamp = params.timestamp
  145. nonce = params.nonce
  146. message = channel.crypto.decrypt_message(web.data(), signature, timestamp, nonce)
  147. except (InvalidSignatureException, InvalidCorpIdException):
  148. raise web.Forbidden()
  149. msg = parse_message(message)
  150. logger.debug("[wechatcom] receive message: {}, msg= {}".format(message, msg))
  151. if msg.type == "event":
  152. if msg.event == "subscribe":
  153. reply_content = subscribe_msg()
  154. if reply_content:
  155. reply = create_reply(reply_content, msg).render()
  156. res = channel.crypto.encrypt_message(reply, nonce, timestamp)
  157. return res
  158. else:
  159. try:
  160. wechatcom_msg = WechatComAppMessage(msg, client=channel.client)
  161. except NotImplementedError as e:
  162. logger.debug("[wechatcom] " + str(e))
  163. return "success"
  164. context = channel._compose_context(
  165. wechatcom_msg.ctype,
  166. wechatcom_msg.content,
  167. isgroup=False,
  168. msg=wechatcom_msg,
  169. )
  170. if context:
  171. channel.produce(context)
  172. return "success"