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.

174 lines
7.1KB

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