|
- # -*- coding: utf-8 -*-
- import io
- import os
- import time
- import imghdr
- import requests
- import asyncio
- import threading
- from config import conf
- from bridge.context import *
- from bridge.reply import *
- from common.log import logger
- from common.singleton import singleton
- from voice.audio_convert import any_to_mp3
- from channel.chat_channel import ChatChannel
- from channel.wechatmp.common import *
- from channel.wechatmp.wechatmp_client import WechatMPClient
- from wechatpy.exceptions import WeChatClientException
- from wechatpy.crypto import WeChatCrypto
-
- import web
- # If using SSL, uncomment the following lines, and modify the certificate path.
- # from cheroot.server import HTTPServer
- # from cheroot.ssl.builtin import BuiltinSSLAdapter
- # HTTPServer.ssl_adapter = BuiltinSSLAdapter(
- # certificate='/ssl/cert.pem',
- # private_key='/ssl/cert.key')
-
-
- @singleton
- class WechatMPChannel(ChatChannel):
- def __init__(self, passive_reply=True):
- super().__init__()
- self.passive_reply = passive_reply
- self.NOT_SUPPORT_REPLYTYPE = []
- appid = conf().get("wechatmp_app_id")
- secret = conf().get("wechatmp_app_secret")
- token = conf().get("wechatmp_token")
- aes_key = conf().get("wechatmp_aes_key")
- self.client = WechatMPClient(appid, secret)
- self.crypto = None
- if aes_key:
- self.crypto = WeChatCrypto(token, aes_key, appid)
- if self.passive_reply:
- # Cache the reply to the user's first message
- self.cache_dict = dict()
- # Record whether the current message is being processed
- self.running = set()
- # Count the request from wechat official server by message_id
- self.request_cnt = dict()
- # The permanent media need to be deleted to avoid media number limit
- self.delete_media_loop = asyncio.new_event_loop()
- t = threading.Thread(target=self.start_loop, args=(self.delete_media_loop,))
- t.setDaemon(True)
- t.start()
-
-
- def startup(self):
- if self.passive_reply:
- urls = ("/wx", "channel.wechatmp.passive_reply.Query")
- else:
- urls = ("/wx", "channel.wechatmp.active_reply.Query")
- app = web.application(urls, globals(), autoreload=False)
- port = conf().get("wechatmp_port", 8080)
- web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port))
-
- def start_loop(self, loop):
- asyncio.set_event_loop(loop)
- loop.run_forever()
-
- async def delete_media(self, media_id):
- logger.debug("[wechatmp] permanent media {} will be deleted in 10s".format(media_id))
- await asyncio.sleep(10)
- self.client.material.delete(media_id)
- logger.info("[wechatmp] permanent media {} has been deleted".format(media_id))
-
- def send(self, reply: Reply, context: Context):
- receiver = context["receiver"]
- if self.passive_reply:
- if reply.type == ReplyType.TEXT or reply.type == ReplyType.INFO or reply.type == ReplyType.ERROR:
- reply_text = reply.content
- logger.info("[wechatmp] text cached, receiver {}\n{}".format(receiver, reply_text))
- self.cache_dict[receiver] = ("text", reply_text)
- elif reply.type == ReplyType.VOICE:
- try:
- voice_file_path = reply.content
- with open(voice_file_path, 'rb') as f:
- # support: <2M, <60s, mp3/wma/wav/amr
- response = self.client.material.add("voice", f)
- logger.debug("[wechatmp] upload voice response: {}".format(response))
- # 根据文件大小估计一个微信自动审核的时间,审核结束前返回将会导致语音无法播放,这个估计有待验证
- f_size = os.fstat(f.fileno()).st_size
- time.sleep(1.0 + 2 * f_size / 1024 / 1024)
- # todo check media_id
- except WeChatClientException as e:
- logger.error("[wechatmp] upload voice failed: {}".format(e))
- return
- media_id = response["media_id"]
- logger.info("[wechatmp] voice uploaded, receiver {}, media_id {}".format(receiver, media_id))
- self.cache_dict[receiver] = ("voice", media_id)
-
- elif reply.type == ReplyType.IMAGE_URL: # 从网络下载图片
- img_url = reply.content
- pic_res = requests.get(img_url, stream=True)
- image_storage = io.BytesIO()
- for block in pic_res.iter_content(1024):
- image_storage.write(block)
- image_storage.seek(0)
- image_type = imghdr.what(image_storage)
- filename = receiver + "-" + str(context['msg'].msg_id) + "." + image_type
- content_type = "image/" + image_type
- try:
- response = self.client.material.add("image", (filename, image_storage, content_type))
- logger.debug("[wechatmp] upload image response: {}".format(response))
- except WeChatClientException as e:
- logger.error("[wechatmp] upload image failed: {}".format(e))
- return
- media_id = response["media_id"]
- logger.info("[wechatmp] image uploaded, receiver {}, media_id {}".format(receiver, media_id))
- self.cache_dict[receiver] = ("image", media_id)
- elif reply.type == ReplyType.IMAGE: # 从文件读取图片
- image_storage = reply.content
- image_storage.seek(0)
- image_type = imghdr.what(image_storage)
- filename = receiver + "-" + str(context['msg'].msg_id) + "." + image_type
- content_type = "image/" + image_type
- try:
- response = self.client.material.add("image", (filename, image_storage, content_type))
- logger.debug("[wechatmp] upload image response: {}".format(response))
- except WeChatClientException as e:
- logger.error("[wechatmp] upload image failed: {}".format(e))
- return
- media_id = response["media_id"]
- logger.info("[wechatmp] image uploaded, receiver {}, media_id {}".format(receiver, media_id))
- self.cache_dict[receiver] = ("image", media_id)
- else:
- if reply.type == ReplyType.TEXT or reply.type == ReplyType.INFO or reply.type == ReplyType.ERROR:
- reply_text = reply.content
- texts = split_string_by_utf8_length(reply_text, MAX_UTF8_LEN)
- if len(texts)>1:
- logger.info("[wechatmp] text too long, split into {} parts".format(len(texts)))
- for text in texts:
- self.client.message.send_text(receiver, text)
- logger.info("[wechatmp] Do send text to {}: {}".format(receiver, reply_text))
- elif reply.type == ReplyType.VOICE:
- try:
- file_path = reply.content
- file_name = os.path.basename(file_path)
- file_type = os.path.splitext(file_name)[1]
- if file_type == ".mp3":
- file_type = "audio/mpeg"
- elif file_type == ".amr":
- file_type = "audio/amr"
- else:
- mp3_file = os.path.splitext(file_path)[0] + ".mp3"
- any_to_mp3(file_path, mp3_file)
- file_path = mp3_file
- file_name = os.path.basename(file_path)
- file_type = "audio/mpeg"
- logger.info("[wechatmp] file_name: {}, file_type: {} ".format(file_name, file_type))
- # support: <2M, <60s, AMR\MP3
- response = self.client.media.upload("voice", (file_name, open(file_path, "rb"), file_type))
- logger.debug("[wechatmp] upload voice response: {}".format(response))
- except WeChatClientException as e:
- logger.error("[wechatmp] upload voice failed: {}".format(e))
- return
- self.client.message.send_voice(receiver, response["media_id"])
- logger.info("[wechatmp] Do send voice to {}".format(receiver))
- elif reply.type == ReplyType.IMAGE_URL: # 从网络下载图片
- img_url = reply.content
- pic_res = requests.get(img_url, stream=True)
- image_storage = io.BytesIO()
- for block in pic_res.iter_content(1024):
- image_storage.write(block)
- image_storage.seek(0)
- image_type = imghdr.what(image_storage)
- filename = receiver + "-" + str(context['msg'].msg_id) + "." + image_type
- content_type = "image/" + image_type
- try:
- response = self.client.media.upload("image", (filename, image_storage, content_type))
- logger.debug("[wechatmp] upload image response: {}".format(response))
- except WeChatClientException as e:
- logger.error("[wechatmp] upload image failed: {}".format(e))
- return
- self.client.message.send_image(receiver, response["media_id"])
- logger.info("[wechatmp] Do send image to {}".format(receiver))
- elif reply.type == ReplyType.IMAGE: # 从文件读取图片
- image_storage = reply.content
- image_storage.seek(0)
- image_type = imghdr.what(image_storage)
- filename = receiver + "-" + str(context['msg'].msg_id) + "." + image_type
- content_type = "image/" + image_type
- try:
- response = self.client.media.upload("image", (filename, image_storage, content_type))
- logger.debug("[wechatmp] upload image response: {}".format(response))
- except WeChatClientException as e:
- logger.error("[wechatmp] upload image failed: {}".format(e))
- return
- self.client.message.send_image(receiver, response["media_id"])
- logger.info("[wechatmp] Do send image to {}".format(receiver))
- return
-
- def _success_callback(self, session_id, context, **kwargs): # 线程异常结束时的回调函数
- logger.debug(
- "[wechatmp] Success to generate reply, msgId={}".format(
- context["msg"].msg_id
- )
- )
- if self.passive_reply:
- self.running.remove(session_id)
-
- def _fail_callback(self, session_id, exception, context, **kwargs): # 线程异常结束时的回调函数
- logger.exception(
- "[wechatmp] Fail to generate reply to user, msgId={}, exception={}".format(
- context["msg"].msg_id, exception
- )
- )
- if self.passive_reply:
- assert session_id not in self.cache_dict
- self.running.remove(session_id)
|