選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

wechatcomapp_channel.py 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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 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(
  34. self.corp_id, self.secret, self.agent_id, self.token, self.aes_key
  35. )
  36. )
  37. self.crypto = WeChatCrypto(self.token, self.aes_key, self.corp_id)
  38. self.client = WeChatClient(self.corp_id, self.secret) # todo: 这里可能有线程安全问题
  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", 8080)
  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. self.client.message.send_text(self.agent_id, receiver, reply.content)
  49. logger.info("[wechatcom] sendMsg={}, receiver={}".format(reply, receiver))
  50. elif reply.type == ReplyType.VOICE:
  51. try:
  52. file_path = reply.content
  53. amr_file = os.path.splitext(file_path)[0] + ".amr"
  54. any_to_amr(file_path, amr_file)
  55. response = self.client.media.upload("voice", open(amr_file, "rb"))
  56. logger.debug("[wechatcom] upload voice response: {}".format(response))
  57. except WeChatClientException as e:
  58. logger.error("[wechatcom] upload voice failed: {}".format(e))
  59. return
  60. try:
  61. os.remove(file_path)
  62. if amr_file != file_path:
  63. os.remove(amr_file)
  64. except Exception:
  65. pass
  66. self.client.message.send_voice(
  67. self.agent_id, receiver, response["media_id"]
  68. )
  69. logger.info(
  70. "[wechatcom] sendVoice={}, receiver={}".format(reply.content, receiver)
  71. )
  72. elif reply.type == ReplyType.IMAGE_URL: # 从网络下载图片
  73. img_url = reply.content
  74. pic_res = requests.get(img_url, stream=True)
  75. image_storage = io.BytesIO()
  76. for block in pic_res.iter_content(1024):
  77. image_storage.write(block)
  78. if (sz := fsize(image_storage)) >= 10 * 1024 * 1024:
  79. logger.info(
  80. "[wechatcom] image too large, ready to compress, sz={}".format(sz)
  81. )
  82. image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
  83. logger.info(
  84. "[wechatcom] image compressed, sz={}".format(fsize(image_storage))
  85. )
  86. image_storage.seek(0)
  87. try:
  88. response = self.client.media.upload("image", image_storage)
  89. logger.debug("[wechatcom] upload image response: {}".format(response))
  90. except WeChatClientException as e:
  91. logger.error("[wechatcom] upload image failed: {}".format(e))
  92. return
  93. self.client.message.send_image(
  94. self.agent_id, receiver, response["media_id"]
  95. )
  96. logger.info(
  97. "[wechatcom] sendImage url={}, receiver={}".format(img_url, receiver)
  98. )
  99. elif reply.type == ReplyType.IMAGE: # 从文件读取图片
  100. image_storage = reply.content
  101. if (sz := fsize(image_storage)) >= 10 * 1024 * 1024:
  102. logger.info(
  103. "[wechatcom] image too large, ready to compress, sz={}".format(sz)
  104. )
  105. image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
  106. logger.info(
  107. "[wechatcom] image compressed, sz={}".format(fsize(image_storage))
  108. )
  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(
  117. self.agent_id, receiver, response["media_id"]
  118. )
  119. logger.info("[wechatcom] sendImage, receiver={}".format(receiver))
  120. class Query:
  121. def GET(self):
  122. channel = WechatComAppChannel()
  123. params = web.input()
  124. logger.info("[wechatcom] receive params: {}".format(params))
  125. try:
  126. signature = params.msg_signature
  127. timestamp = params.timestamp
  128. nonce = params.nonce
  129. echostr = params.echostr
  130. echostr = channel.crypto.check_signature(
  131. signature, timestamp, nonce, echostr
  132. )
  133. except InvalidSignatureException:
  134. raise web.Forbidden()
  135. return echostr
  136. def POST(self):
  137. channel = WechatComAppChannel()
  138. params = web.input()
  139. logger.info("[wechatcom] receive params: {}".format(params))
  140. try:
  141. signature = params.msg_signature
  142. timestamp = params.timestamp
  143. nonce = params.nonce
  144. message = channel.crypto.decrypt_message(
  145. web.data(), signature, timestamp, nonce
  146. )
  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. trigger_prefix = conf().get("single_chat_prefix", [""])[0]
  154. reply_content = textwrap.dedent(
  155. f"""\
  156. 感谢您的关注!
  157. 这里是ChatGPT,可以自由对话。
  158. 支持语音对话。
  159. 支持通用表情输入。
  160. 支持图片输入输出。
  161. 支持角色扮演和文字冒险两种定制模式对话。
  162. 输入'{trigger_prefix}#help' 查看详细指令。"""
  163. )
  164. reply = create_reply(reply_content, msg).render()
  165. res = channel.crypto.encrypt_message(reply, nonce, timestamp)
  166. return res
  167. else:
  168. try:
  169. wechatcom_msg = WechatComAppMessage(msg, client=channel.client)
  170. except NotImplementedError as e:
  171. logger.debug("[wechatcom] " + str(e))
  172. return "success"
  173. context = channel._compose_context(
  174. wechatcom_msg.ctype,
  175. wechatcom_msg.content,
  176. isgroup=False,
  177. msg=wechatcom_msg,
  178. )
  179. if context:
  180. channel.produce(context)
  181. return "success"