wechatcomapp_channel.py 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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
  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. try:
  95. response = self.client.media.upload("image", image_storage)
  96. logger.debug("[wechatcom] upload image response: {}".format(response))
  97. except WeChatClientException as e:
  98. logger.error("[wechatcom] upload image failed: {}".format(e))
  99. return
  100. self.client.message.send_image(self.agent_id, receiver, response["media_id"])
  101. logger.info("[wechatcom] sendImage url={}, receiver={}".format(img_url, receiver))
  102. elif reply.type == ReplyType.IMAGE: # 从文件读取图片
  103. image_storage = reply.content
  104. sz = fsize(image_storage)
  105. if sz >= 10 * 1024 * 1024:
  106. logger.info("[wechatcom] image too large, ready to compress, sz={}".format(sz))
  107. image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
  108. logger.info("[wechatcom] image compressed, sz={}".format(fsize(image_storage)))
  109. image_storage.seek(0)
  110. try:
  111. response = self.client.media.upload("image", image_storage)
  112. logger.debug("[wechatcom] upload image response: {}".format(response))
  113. except WeChatClientException as e:
  114. logger.error("[wechatcom] upload image failed: {}".format(e))
  115. return
  116. self.client.message.send_image(self.agent_id, receiver, response["media_id"])
  117. logger.info("[wechatcom] sendImage, receiver={}".format(receiver))
  118. class Query:
  119. def GET(self):
  120. channel = WechatComAppChannel()
  121. params = web.input()
  122. logger.info("[wechatcom] receive params: {}".format(params))
  123. try:
  124. signature = params.msg_signature
  125. timestamp = params.timestamp
  126. nonce = params.nonce
  127. echostr = params.echostr
  128. echostr = channel.crypto.check_signature(signature, timestamp, nonce, echostr)
  129. except InvalidSignatureException:
  130. raise web.Forbidden()
  131. return echostr
  132. def POST(self):
  133. channel = WechatComAppChannel()
  134. params = web.input()
  135. logger.info("[wechatcom] receive params: {}".format(params))
  136. try:
  137. signature = params.msg_signature
  138. timestamp = params.timestamp
  139. nonce = params.nonce
  140. message = channel.crypto.decrypt_message(web.data(), signature, timestamp, nonce)
  141. except (InvalidSignatureException, InvalidCorpIdException):
  142. raise web.Forbidden()
  143. msg = parse_message(message)
  144. logger.debug("[wechatcom] receive message: {}, msg= {}".format(message, msg))
  145. if msg.type == "event":
  146. if msg.event == "subscribe":
  147. reply_content = subscribe_msg()
  148. if reply_content:
  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"