@@ -1,6 +1,8 @@ | |||||
# 企业微信应用号channel | # 企业微信应用号channel | ||||
企业微信官方提供了客服、应用等API,本channel使用的是企业微信的应用API的能力。因为未来可能还会开发客服能力,所以本channel的类型名叫作`wechatcom_app`。 | |||||
企业微信官方提供了客服、应用等API,本channel使用的是企业微信的应用API的能力。 | |||||
因为未来可能还会开发客服能力,所以本channel的类型名叫作`wechatcom_app`。 | |||||
`wechatcom_app` channel支持插件系统和图片声音交互等能力,除了无法加入群聊,作为个人使用的私人助理已绰绰有余。 | `wechatcom_app` channel支持插件系统和图片声音交互等能力,除了无法加入群聊,作为个人使用的私人助理已绰绰有余。 | ||||
@@ -29,7 +31,7 @@ | |||||
- 在详情页如果点击`企业可信IP`的配置(没看到可以不管),填入你服务器的公网IP | - 在详情页如果点击`企业可信IP`的配置(没看到可以不管),填入你服务器的公网IP | ||||
- 点击`接收消息`下的启用API接收消息 | - 点击`接收消息`下的启用API接收消息 | ||||
- `URL`填写格式为`http://url:port/wxcomapp`,是程序监听的端口,默认是9898 | |||||
- `URL`填写格式为`http://url:port/wxcomapp`,`port`是程序监听的端口,默认是9898 | |||||
如果是未认证的企业,url可直接使用服务器的IP。如果是认证企业,需要使用备案的域名,可使用二级域名。 | 如果是未认证的企业,url可直接使用服务器的IP。如果是认证企业,需要使用备案的域名,可使用二级域名。 | ||||
- `Token`可随意填写,停留在这个页面 | - `Token`可随意填写,停留在这个页面 | ||||
- 在程序根目录`config.json`中增加配置(**去掉注释**),`wechatcomapp_aes_key`是当前页面的`wechatcomapp_aes_key` | - 在程序根目录`config.json`中增加配置(**去掉注释**),`wechatcomapp_aes_key`是当前页面的`wechatcomapp_aes_key` | ||||
@@ -2,6 +2,7 @@ | |||||
import io | import io | ||||
import os | import os | ||||
import textwrap | import textwrap | ||||
import time | |||||
import requests | import requests | ||||
import web | import web | ||||
@@ -17,10 +18,12 @@ from channel.wechatcom.wechatcomapp_client import WechatComAppClient | |||||
from channel.wechatcom.wechatcomapp_message import WechatComAppMessage | from channel.wechatcom.wechatcomapp_message import WechatComAppMessage | ||||
from common.log import logger | from common.log import logger | ||||
from common.singleton import singleton | from common.singleton import singleton | ||||
from common.utils import compress_imgfile, fsize | |||||
from common.utils import compress_imgfile, fsize, split_string_by_utf8_length | |||||
from config import conf | from config import conf | ||||
from voice.audio_convert import any_to_amr | from voice.audio_convert import any_to_amr | ||||
MAX_UTF8_LEN = 2048 | |||||
@singleton | @singleton | ||||
class WechatComAppChannel(ChatChannel): | class WechatComAppChannel(ChatChannel): | ||||
@@ -50,8 +53,15 @@ class WechatComAppChannel(ChatChannel): | |||||
def send(self, reply: Reply, context: Context): | def send(self, reply: Reply, context: Context): | ||||
receiver = context["receiver"] | receiver = context["receiver"] | ||||
if reply.type in [ReplyType.TEXT, ReplyType.ERROR, ReplyType.INFO]: | if reply.type in [ReplyType.TEXT, ReplyType.ERROR, ReplyType.INFO]: | ||||
self.client.message.send_text(self.agent_id, receiver, reply.content) | |||||
logger.info("[wechatcom] sendMsg={}, receiver={}".format(reply, receiver)) | |||||
reply_text = reply.content | |||||
texts = split_string_by_utf8_length(reply_text, MAX_UTF8_LEN) | |||||
if len(texts) > 1: | |||||
logger.info("[wechatcom] text too long, split into {} parts".format(len(texts))) | |||||
for i, text in enumerate(texts): | |||||
self.client.message.send_text(self.agent_id, receiver, text) | |||||
if i != len(texts) - 1: | |||||
time.sleep(0.5) # 休眠0.5秒,防止发送过快乱序 | |||||
logger.info("[wechatcom] Do send text to {}: {}".format(receiver, reply_text)) | |||||
elif reply.type == ReplyType.VOICE: | elif reply.type == ReplyType.VOICE: | ||||
try: | try: | ||||
file_path = reply.content | file_path = reply.content | ||||
@@ -43,20 +43,3 @@ def subscribe_msg(): | |||||
输入'{trigger_prefix}#帮助' 查看详细指令。""" | 输入'{trigger_prefix}#帮助' 查看详细指令。""" | ||||
) | ) | ||||
return msg | return msg | ||||
def split_string_by_utf8_length(string, max_length, max_split=0): | |||||
encoded = string.encode("utf-8") | |||||
start, end = 0, 0 | |||||
result = [] | |||||
while end < len(encoded): | |||||
if max_split > 0 and len(result) >= max_split: | |||||
result.append(encoded[start:].decode("utf-8")) | |||||
break | |||||
end = min(start + max_length, len(encoded)) | |||||
# 如果当前字节不是 UTF-8 编码的开始字节,则向前查找直到找到开始字节为止 | |||||
while end < len(encoded) and (encoded[end] & 0b11000000) == 0b10000000: | |||||
end -= 1 | |||||
result.append(encoded[start:end].decode("utf-8")) | |||||
start = end | |||||
return result |
@@ -11,6 +11,7 @@ from channel.wechatmp.common import * | |||||
from channel.wechatmp.wechatmp_channel import WechatMPChannel | from channel.wechatmp.wechatmp_channel import WechatMPChannel | ||||
from channel.wechatmp.wechatmp_message import WeChatMPMessage | from channel.wechatmp.wechatmp_message import WeChatMPMessage | ||||
from common.log import logger | from common.log import logger | ||||
from common.utils import split_string_by_utf8_length | |||||
from config import conf | from config import conf | ||||
@@ -18,6 +18,7 @@ from channel.wechatmp.common import * | |||||
from channel.wechatmp.wechatmp_client import WechatMPClient | from channel.wechatmp.wechatmp_client import WechatMPClient | ||||
from common.log import logger | from common.log import logger | ||||
from common.singleton import singleton | from common.singleton import singleton | ||||
from common.utils import split_string_by_utf8_length | |||||
from config import conf | from config import conf | ||||
from voice.audio_convert import any_to_mp3 | from voice.audio_convert import any_to_mp3 | ||||
@@ -140,8 +141,10 @@ class WechatMPChannel(ChatChannel): | |||||
texts = split_string_by_utf8_length(reply_text, MAX_UTF8_LEN) | texts = split_string_by_utf8_length(reply_text, MAX_UTF8_LEN) | ||||
if len(texts) > 1: | if len(texts) > 1: | ||||
logger.info("[wechatmp] text too long, split into {} parts".format(len(texts))) | logger.info("[wechatmp] text too long, split into {} parts".format(len(texts))) | ||||
for text in texts: | |||||
for i, text in enumerate(texts): | |||||
self.client.message.send_text(receiver, text) | self.client.message.send_text(receiver, text) | ||||
if i != len(texts) - 1: | |||||
time.sleep(0.5) # 休眠0.5秒,防止发送过快乱序 | |||||
logger.info("[wechatmp] Do send text to {}: {}".format(receiver, reply_text)) | logger.info("[wechatmp] Do send text to {}: {}".format(receiver, reply_text)) | ||||
elif reply.type == ReplyType.VOICE: | elif reply.type == ReplyType.VOICE: | ||||
try: | try: | ||||
@@ -32,3 +32,20 @@ def compress_imgfile(file, max_size): | |||||
if fsize(out_buf) <= max_size: | if fsize(out_buf) <= max_size: | ||||
return out_buf | return out_buf | ||||
quality -= 5 | quality -= 5 | ||||
def split_string_by_utf8_length(string, max_length, max_split=0): | |||||
encoded = string.encode("utf-8") | |||||
start, end = 0, 0 | |||||
result = [] | |||||
while end < len(encoded): | |||||
if max_split > 0 and len(result) >= max_split: | |||||
result.append(encoded[start:].decode("utf-8")) | |||||
break | |||||
end = min(start + max_length, len(encoded)) | |||||
# 如果当前字节不是 UTF-8 编码的开始字节,则向前查找直到找到开始字节为止 | |||||
while end < len(encoded) and (encoded[end] & 0b11000000) == 0b10000000: | |||||
end -= 1 | |||||
result.append(encoded[start:end].decode("utf-8")) | |||||
start = end | |||||
return result |