mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-03-19 21:38:18 +08:00
docs: update README.md for wechatcom_app
This commit is contained in:
@@ -1,20 +1,42 @@
|
|||||||
|
# 企业微信应用号channel
|
||||||
|
|
||||||
> 详细文档暂无
|
企业微信官方提供了客服、应用等API,本channel使用的是企业微信的应用API的能力。因为未来可能还会开发客服能力,所以本channel的类型名叫作`wechatcom_app`。
|
||||||
|
|
||||||
## 自建应用
|
`wechatcom_app` channel支持插件系统和图片声音交互等能力,除了无法加入群聊,作为个人使用的私人助理已绰绰有余。
|
||||||
|
|
||||||
- 在企业微信工作台自建应用
|
## 开始之前
|
||||||
|
|
||||||
建立应用后点击通过API接收消息,设置服务器地址,服务器地址是`http://url:port/wxcomapp`的形式,也可以不用域名,比如 `http://ip:port/wxcomapp`
|
- 在企业中确认自己拥有在企业内自建应用的权限。
|
||||||
|
- 如果没有权限或者是个人用户,也可创建未认证的企业。操作方式:登录手机企业微信,选择`创建/加入企业`来创建企业,类型请选择企业,企业名称可随意填写。
|
||||||
|
未认证的企业有100人的服务人数上限,其他功能与认证企业没有差异。
|
||||||
|
|
||||||
- 修改配置
|
本channel需安装的依赖与公众号一致,需要安装`wechatpy`和`web.py`,它们包含在`requirements-optional.txt`中。
|
||||||
|
|
||||||
在主目录下的`config.json`中填写以下配置项
|
## 使用方法
|
||||||
|
|
||||||
|
1.查看企业ID
|
||||||
|
|
||||||
|
- 扫码登陆[企业微信后台](https://work.weixin.qq.com)
|
||||||
|
- 选择`我的企业`,点击`企业信息`,记住该`企业ID`
|
||||||
|
|
||||||
|
2.创建自建应用
|
||||||
|
|
||||||
|
- 选择应用管理, 在自建区选创建应用来创建企业自建应用
|
||||||
|
- 上传应用logo,填写应用名称等项
|
||||||
|
- 创建应用后进入应用详情页面,记住`AgentId`和`Secert`
|
||||||
|
|
||||||
|
3.配置应用
|
||||||
|
|
||||||
|
- 在详情页如果点击`企业可信IP`的配置(没看到可以不管),填入你服务器的公网IP
|
||||||
|
- 点击`接收消息`下的启用API接收消息
|
||||||
|
- `URL`填写格式为`http://url:port/wxcomapp`,是程序监听的端口,默认是9898
|
||||||
|
如果是未认证的企业,url可直接使用服务器的IP。如果是认证企业,需要使用备案的域名,可使用二级域名。
|
||||||
|
- `Token`可随意填写,停留在这个页面
|
||||||
|
- 在程序根目录`config.json`中增加配置(**去掉注释**),`wechatcomapp_aes_key`是当前页面的`wechatcomapp_aes_key`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# wechatcom的通用配置
|
"channel_type": "wechatcom_app",
|
||||||
"wechatcom_corp_id": "", # 企业微信公司的corpID
|
"wechatcom_corp_id": "", # 企业微信公司的corpID
|
||||||
# wechatcomapp的配置
|
|
||||||
"wechatcomapp_token": "", # 企业微信app的token
|
"wechatcomapp_token": "", # 企业微信app的token
|
||||||
"wechatcomapp_port": 9898, # 企业微信app的服务端口, 不需要端口转发
|
"wechatcomapp_port": 9898, # 企业微信app的服务端口, 不需要端口转发
|
||||||
"wechatcomapp_secret": "", # 企业微信app的secret
|
"wechatcomapp_secret": "", # 企业微信app的secret
|
||||||
@@ -22,12 +44,14 @@
|
|||||||
"wechatcomapp_aes_key": "", # 企业微信app的aes_key
|
"wechatcomapp_aes_key": "", # 企业微信app的aes_key
|
||||||
```
|
```
|
||||||
|
|
||||||
- 运行程序
|
- 运行程序,在页面中点击保存,保存成功说明验证成功
|
||||||
|
|
||||||
```python app.py```
|
4.连接个人微信
|
||||||
|
|
||||||
在设置服务器页面点击保存
|
选择`我的企业`,点击`微信插件`,下面有个邀请关注的二维码。微信扫码后,即可在微信中看到对应企业,在这里你便可以和机器人沟通。
|
||||||
|
|
||||||
- 添加可信IP
|
## 测试体验
|
||||||
|
|
||||||
在自建应用管理页下方,将服务器的IP添加到可信IP
|
AIGC开放社区中已经部署了多个可免费使用的Bot,扫描下方的二维码会自动邀请你来体验。
|
||||||
|
|
||||||
|
<img width="360" src="./docs/images/aigcopen.png">
|
||||||
@@ -35,9 +35,7 @@ class WechatComAppChannel(ChatChannel):
|
|||||||
self.aes_key = conf().get("wechatcomapp_aes_key")
|
self.aes_key = conf().get("wechatcomapp_aes_key")
|
||||||
print(self.corp_id, self.secret, self.agent_id, self.token, self.aes_key)
|
print(self.corp_id, self.secret, self.agent_id, self.token, self.aes_key)
|
||||||
logger.info(
|
logger.info(
|
||||||
"[wechatcom] init: corp_id: {}, secret: {}, agent_id: {}, token: {}, aes_key: {}".format(
|
"[wechatcom] init: corp_id: {}, secret: {}, agent_id: {}, token: {}, aes_key: {}".format(self.corp_id, self.secret, self.agent_id, self.token, self.aes_key)
|
||||||
self.corp_id, self.secret, self.agent_id, self.token, self.aes_key
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
self.crypto = WeChatCrypto(self.token, self.aes_key, self.corp_id)
|
self.crypto = WeChatCrypto(self.token, self.aes_key, self.corp_id)
|
||||||
self.client = WeChatClient(self.corp_id, self.secret) # todo: 这里可能有线程安全问题
|
self.client = WeChatClient(self.corp_id, self.secret) # todo: 这里可能有线程安全问题
|
||||||
@@ -46,7 +44,7 @@ class WechatComAppChannel(ChatChannel):
|
|||||||
# start message listener
|
# start message listener
|
||||||
urls = ("/wxcomapp", "channel.wechatcom.wechatcomapp_channel.Query")
|
urls = ("/wxcomapp", "channel.wechatcom.wechatcomapp_channel.Query")
|
||||||
app = web.application(urls, globals(), autoreload=False)
|
app = web.application(urls, globals(), autoreload=False)
|
||||||
port = conf().get("wechatcomapp_port", 8080)
|
port = conf().get("wechatcomapp_port", 9898)
|
||||||
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port))
|
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port))
|
||||||
|
|
||||||
def send(self, reply: Reply, context: Context):
|
def send(self, reply: Reply, context: Context):
|
||||||
@@ -70,12 +68,8 @@ class WechatComAppChannel(ChatChannel):
|
|||||||
os.remove(amr_file)
|
os.remove(amr_file)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self.client.message.send_voice(
|
self.client.message.send_voice(self.agent_id, receiver, response["media_id"])
|
||||||
self.agent_id, receiver, response["media_id"]
|
logger.info("[wechatcom] sendVoice={}, receiver={}".format(reply.content, receiver))
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
"[wechatcom] sendVoice={}, receiver={}".format(reply.content, receiver)
|
|
||||||
)
|
|
||||||
elif reply.type == ReplyType.IMAGE_URL: # 从网络下载图片
|
elif reply.type == ReplyType.IMAGE_URL: # 从网络下载图片
|
||||||
img_url = reply.content
|
img_url = reply.content
|
||||||
pic_res = requests.get(img_url, stream=True)
|
pic_res = requests.get(img_url, stream=True)
|
||||||
@@ -83,13 +77,9 @@ class WechatComAppChannel(ChatChannel):
|
|||||||
for block in pic_res.iter_content(1024):
|
for block in pic_res.iter_content(1024):
|
||||||
image_storage.write(block)
|
image_storage.write(block)
|
||||||
if (sz := fsize(image_storage)) >= 10 * 1024 * 1024:
|
if (sz := fsize(image_storage)) >= 10 * 1024 * 1024:
|
||||||
logger.info(
|
logger.info("[wechatcom] image too large, ready to compress, sz={}".format(sz))
|
||||||
"[wechatcom] image too large, ready to compress, sz={}".format(sz)
|
|
||||||
)
|
|
||||||
image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
|
image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
|
||||||
logger.info(
|
logger.info("[wechatcom] image compressed, sz={}".format(fsize(image_storage)))
|
||||||
"[wechatcom] image compressed, sz={}".format(fsize(image_storage))
|
|
||||||
)
|
|
||||||
image_storage.seek(0)
|
image_storage.seek(0)
|
||||||
try:
|
try:
|
||||||
response = self.client.media.upload("image", image_storage)
|
response = self.client.media.upload("image", image_storage)
|
||||||
@@ -98,22 +88,14 @@ class WechatComAppChannel(ChatChannel):
|
|||||||
logger.error("[wechatcom] upload image failed: {}".format(e))
|
logger.error("[wechatcom] upload image failed: {}".format(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.client.message.send_image(
|
self.client.message.send_image(self.agent_id, receiver, response["media_id"])
|
||||||
self.agent_id, receiver, response["media_id"]
|
logger.info("[wechatcom] sendImage url={}, receiver={}".format(img_url, receiver))
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
"[wechatcom] sendImage url={}, receiver={}".format(img_url, receiver)
|
|
||||||
)
|
|
||||||
elif reply.type == ReplyType.IMAGE: # 从文件读取图片
|
elif reply.type == ReplyType.IMAGE: # 从文件读取图片
|
||||||
image_storage = reply.content
|
image_storage = reply.content
|
||||||
if (sz := fsize(image_storage)) >= 10 * 1024 * 1024:
|
if (sz := fsize(image_storage)) >= 10 * 1024 * 1024:
|
||||||
logger.info(
|
logger.info("[wechatcom] image too large, ready to compress, sz={}".format(sz))
|
||||||
"[wechatcom] image too large, ready to compress, sz={}".format(sz)
|
|
||||||
)
|
|
||||||
image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
|
image_storage = compress_imgfile(image_storage, 10 * 1024 * 1024 - 1)
|
||||||
logger.info(
|
logger.info("[wechatcom] image compressed, sz={}".format(fsize(image_storage)))
|
||||||
"[wechatcom] image compressed, sz={}".format(fsize(image_storage))
|
|
||||||
)
|
|
||||||
image_storage.seek(0)
|
image_storage.seek(0)
|
||||||
try:
|
try:
|
||||||
response = self.client.media.upload("image", image_storage)
|
response = self.client.media.upload("image", image_storage)
|
||||||
@@ -121,9 +103,7 @@ class WechatComAppChannel(ChatChannel):
|
|||||||
except WeChatClientException as e:
|
except WeChatClientException as e:
|
||||||
logger.error("[wechatcom] upload image failed: {}".format(e))
|
logger.error("[wechatcom] upload image failed: {}".format(e))
|
||||||
return
|
return
|
||||||
self.client.message.send_image(
|
self.client.message.send_image(self.agent_id, receiver, response["media_id"])
|
||||||
self.agent_id, receiver, response["media_id"]
|
|
||||||
)
|
|
||||||
logger.info("[wechatcom] sendImage, receiver={}".format(receiver))
|
logger.info("[wechatcom] sendImage, receiver={}".format(receiver))
|
||||||
|
|
||||||
|
|
||||||
@@ -137,9 +117,7 @@ class Query:
|
|||||||
timestamp = params.timestamp
|
timestamp = params.timestamp
|
||||||
nonce = params.nonce
|
nonce = params.nonce
|
||||||
echostr = params.echostr
|
echostr = params.echostr
|
||||||
echostr = channel.crypto.check_signature(
|
echostr = channel.crypto.check_signature(signature, timestamp, nonce, echostr)
|
||||||
signature, timestamp, nonce, echostr
|
|
||||||
)
|
|
||||||
except InvalidSignatureException:
|
except InvalidSignatureException:
|
||||||
raise web.Forbidden()
|
raise web.Forbidden()
|
||||||
return echostr
|
return echostr
|
||||||
@@ -152,9 +130,7 @@ class Query:
|
|||||||
signature = params.msg_signature
|
signature = params.msg_signature
|
||||||
timestamp = params.timestamp
|
timestamp = params.timestamp
|
||||||
nonce = params.nonce
|
nonce = params.nonce
|
||||||
message = channel.crypto.decrypt_message(
|
message = channel.crypto.decrypt_message(web.data(), signature, timestamp, nonce)
|
||||||
web.data(), signature, timestamp, nonce
|
|
||||||
)
|
|
||||||
except (InvalidSignatureException, InvalidCorpIdException):
|
except (InvalidSignatureException, InvalidCorpIdException):
|
||||||
raise web.Forbidden()
|
raise web.Forbidden()
|
||||||
msg = parse_message(message)
|
msg = parse_message(message)
|
||||||
|
|||||||
BIN
docs/images/aigcopen.png
Normal file
BIN
docs/images/aigcopen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Reference in New Issue
Block a user