Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

164 linhas
8.1KB

  1. # -*- coding: utf-8 -*-
  2. import io
  3. import os
  4. import time
  5. import imghdr
  6. import requests
  7. from bridge.context import *
  8. from bridge.reply import *
  9. from channel.chat_channel import ChatChannel
  10. from channel.wechatmp.wechatmp_client import WechatMPClient
  11. from channel.wechatmp.common import *
  12. from common.log import logger
  13. from common.singleton import singleton
  14. from config import conf
  15. import asyncio
  16. from threading import Thread
  17. import web
  18. # If using SSL, uncomment the following lines, and modify the certificate path.
  19. # from cheroot.server import HTTPServer
  20. # from cheroot.ssl.builtin import BuiltinSSLAdapter
  21. # HTTPServer.ssl_adapter = BuiltinSSLAdapter(
  22. # certificate='/ssl/cert.pem',
  23. # private_key='/ssl/cert.key')
  24. @singleton
  25. class WechatMPChannel(ChatChannel):
  26. def __init__(self, passive_reply=True):
  27. super().__init__()
  28. self.passive_reply = passive_reply
  29. self.NOT_SUPPORT_REPLYTYPE = []
  30. self.client = WechatMPClient()
  31. if self.passive_reply:
  32. # Cache the reply to the user's first message
  33. self.cache_dict = dict()
  34. # Record whether the current message is being processed
  35. self.running = set()
  36. # Count the request from wechat official server by message_id
  37. self.request_cnt = dict()
  38. # The permanent media need to be deleted to avoid media number limit
  39. self.delete_media_loop = asyncio.new_event_loop()
  40. t = Thread(target=self.start_loop, args=(self.delete_media_loop,))
  41. t.setDaemon(True)
  42. t.start()
  43. def startup(self):
  44. if self.passive_reply:
  45. urls = ("/wx", "channel.wechatmp.passive_reply.Query")
  46. else:
  47. urls = ("/wx", "channel.wechatmp.active_reply.Query")
  48. app = web.application(urls, globals(), autoreload=False)
  49. port = conf().get("wechatmp_port", 8080)
  50. web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port))
  51. def start_loop(self, loop):
  52. asyncio.set_event_loop(loop)
  53. loop.run_forever()
  54. async def delete_media(self, media_id):
  55. logger.debug("[wechatmp] permanent media {} will be deleted in 10s".format(media_id))
  56. await asyncio.sleep(10)
  57. self.client.delete_permanent_media(media_id)
  58. logger.info("[wechatmp] permanent media {} has been deleted".format(media_id))
  59. def send(self, reply: Reply, context: Context):
  60. receiver = context["receiver"]
  61. if self.passive_reply:
  62. if reply.type == ReplyType.TEXT or reply.type == ReplyType.INFO or reply.type == ReplyType.ERROR:
  63. reply_text = reply.content
  64. logger.info("[wechatmp] text cached, receiver {}\n{}".format(receiver, reply_text))
  65. self.cache_dict[receiver] = ("text", reply_text)
  66. elif reply.type == ReplyType.VOICE:
  67. voice_file_path = reply.content
  68. logger.debug("[wechatmp] voice file path {}".format(voice_file_path))
  69. with open(voice_file_path, 'rb') as f:
  70. filename = receiver + "-" + context["msg"].msg_id + ".mp3"
  71. media_id = self.client.upload_permanent_media("voice", (filename, f, "audio/mpeg"))
  72. # 根据文件大小估计一个微信自动审核的时间,审核结束前返回将会导致语音无法播放,这个估计有待验证
  73. f_size = os.fstat(f.fileno()).st_size
  74. time.sleep(1.0 + 2 * f_size / 1024 / 1024)
  75. logger.info("[wechatmp] voice uploaded, receiver {}, media_id {}".format(receiver, media_id))
  76. self.cache_dict[receiver] = ("voice", media_id)
  77. elif reply.type == ReplyType.IMAGE_URL: # 从网络下载图片
  78. img_url = reply.content
  79. pic_res = requests.get(img_url, stream=True)
  80. image_storage = io.BytesIO()
  81. for block in pic_res.iter_content(1024):
  82. image_storage.write(block)
  83. image_storage.seek(0)
  84. image_type = imghdr.what(image_storage)
  85. filename = receiver + "-" + context["msg"].msg_id + "." + image_type
  86. content_type = "image/" + image_type
  87. media_id = self.client.upload_permanent_media("image", (filename, image_storage, content_type))
  88. logger.info("[wechatmp] image uploaded, receiver {}, media_id {}".format(receiver, media_id))
  89. self.cache_dict[receiver] = ("image", media_id)
  90. elif reply.type == ReplyType.IMAGE: # 从文件读取图片
  91. image_storage = reply.content
  92. image_storage.seek(0)
  93. image_type = imghdr.what(image_storage)
  94. filename = receiver + "-" + context["msg"].msg_id + "." + image_type
  95. content_type = "image/" + image_type
  96. media_id = self.client.upload_permanent_media("image", (filename, image_storage, content_type))
  97. logger.info("[wechatmp] image uploaded, receiver {}, media_id {}".format(receiver, media_id))
  98. self.cache_dict[receiver] = ("image", media_id)
  99. else:
  100. if reply.type == ReplyType.TEXT or reply.type == ReplyType.INFO or reply.type == ReplyType.ERROR:
  101. reply_text = reply.content
  102. self.client.send_text(receiver, reply_text)
  103. logger.info("[wechatmp] Do send text to {}: {}".format(receiver, reply_text))
  104. elif reply.type == ReplyType.VOICE:
  105. voice_file_path = reply.content
  106. logger.debug("[wechatmp] voice file path {}".format(voice_file_path))
  107. with open(voice_file_path, 'rb') as f:
  108. filename = receiver + "-" + context["msg"].msg_id + ".mp3"
  109. media_id = self.client.upload_media("voice", (filename, f, "audio/mpeg"))
  110. self.client.send_voice(receiver, media_id)
  111. logger.info("[wechatmp] Do send voice to {}".format(receiver))
  112. elif reply.type == ReplyType.IMAGE_URL: # 从网络下载图片
  113. img_url = reply.content
  114. pic_res = requests.get(img_url, stream=True)
  115. image_storage = io.BytesIO()
  116. for block in pic_res.iter_content(1024):
  117. image_storage.write(block)
  118. image_storage.seek(0)
  119. image_type = imghdr.what(image_storage)
  120. filename = receiver + "-" + context["msg"].msg_id + "." + image_type
  121. content_type = "image/" + image_type
  122. # content_type = pic_res.headers.get('content-type')
  123. media_id = self.client.upload_media("image", (filename, image_storage, content_type))
  124. self.client.send_image(receiver, media_id)
  125. logger.info("[wechatmp] Do send image to {}".format(receiver))
  126. elif reply.type == ReplyType.IMAGE: # 从文件读取图片
  127. image_storage = reply.content
  128. image_storage.seek(0)
  129. image_type = imghdr.what(image_storage)
  130. filename = receiver + "-" + context["msg"].msg_id + "." + image_type
  131. content_type = "image/" + image_type
  132. media_id = self.client.upload_media("image", (filename, image_storage, content_type))
  133. self.client.send_image(receiver, media_id)
  134. logger.info("[wechatmp] Do send image to {}".format(receiver))
  135. return
  136. def _success_callback(self, session_id, context, **kwargs): # 线程异常结束时的回调函数
  137. logger.debug(
  138. "[wechatmp] Success to generate reply, msgId={}".format(
  139. context["msg"].msg_id
  140. )
  141. )
  142. if self.passive_reply:
  143. self.running.remove(session_id)
  144. def _fail_callback(self, session_id, exception, context, **kwargs): # 线程异常结束时的回调函数
  145. logger.exception(
  146. "[wechatmp] Fail to generate reply to user, msgId={}, exception={}".format(
  147. context["msg"].msg_id, exception
  148. )
  149. )
  150. if self.passive_reply:
  151. assert session_id not in self.cache_dict
  152. self.running.remove(session_id)