diff --git a/README.md b/README.md index 8351f3a..247ba7a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ - [ ] 企业微信 - [x] [Telegram](https://github.com/zhayujie/bot-on-anything#6telegram) - [x] [QQ](https://github.com/zhayujie/bot-on-anything#5qq) - - [ ] 钉钉 + - [x] 钉钉 - [ ] 飞书 - [x] [Gmail](https://github.com/zhayujie/bot-on-anything#7gmail) - [x] [Slack](https://github.com/zhayujie/bot-on-anything#8slack) @@ -492,6 +492,35 @@ pip3 install PyJWT flask 服务器运行:部署后访问 `http://公网域名或IP:端口` +### 10.钉钉 + +**依赖** + +```bash +pip3 install requests flask +``` +**配置** + +```bash +"channel": { + "type": "dingtalk", + "dingtalk": { + "image_create_prefix": ["画", "draw", "Draw"], + "port": "8081", //对外端口 + "dingtalk_token": "xx", //webhook地址的access_token + "dingtalk_post_token": "xx", //钉钉post回消息时header中带的检验token + "dingtalk_secret": "xx"// 安全加密加签串,群机器人中 + } + } +``` +钉钉开放平台说明: https://open.dingtalk.com/document/robots/customize-robot-security-settin.dingtalk.com/robot/send?access_token=906dadcbc7750fef5ff60d3445b66d5bbca32804f40fbdb59039a29b20b9a3f0gs + +https://open.dingtalk.com/document/orgapp/custom-robot-access + +**生成机器人** + +地址: https://open-dev.dingtalk.com/fe/app#/corp/robot +添加机器人,在开发管理中设置服务器出口ip(在部署机执行curl ifconfig.me就可以得到)和消息接收地址(配置中的对外地址如 https://xx.xx.com:8081) ### 通用配置 diff --git a/channel/channel_factory.py b/channel/channel_factory.py index 7b53e80..78a1a49 100644 --- a/channel/channel_factory.py +++ b/channel/channel_factory.py @@ -45,5 +45,9 @@ def create_channel(channel_type): from channel.http.http_channel import HttpChannel return HttpChannel() + elif channel_type == const.DINGTALK: + from channel.dingtalk.dingtalk_channel import DingTalkChannel + return DingTalkChannel() + else: raise RuntimeError("unknown channel_type in config.json: " + channel_type) diff --git a/channel/dingtalk/dingtalk_channel.py b/channel/dingtalk/dingtalk_channel.py new file mode 100644 index 0000000..47c5d7f --- /dev/null +++ b/channel/dingtalk/dingtalk_channel.py @@ -0,0 +1,101 @@ +# encoding:utf-8 +import json +import hmac +import hashlib +import base64 +import time +import requests +from urllib.parse import quote_plus +from common import log +from flask import Flask, request, render_template, make_response +from common import const +from common import functions +from config import channel_conf +from config import channel_conf_val +from channel.channel import Channel + +class DingTalkChannel(Channel): + def __init__(self): + self.dingtalk_token = channel_conf(const.DINGTALK).get('dingtalk_token') + self.dingtalk_post_token = channel_conf(const.DINGTALK).get('dingtalk_post_token') + self.dingtalk_secret = channel_conf(const.DINGTALK).get('dingtalk_secret') + log.info("[DingTalk] dingtalk_secret={}, dingtalk_token={} dingtalk_post_token={}".format(self.dingtalk_secret, self.dingtalk_token, self.dingtalk_post_token)) + + def startup(self): + + http_app.run(host='0.0.0.0', port=channel_conf(const.DINGTALK).get('port')) + + def notify_dingtalk(self, answer): + data = { + "msgtype": "text", + "text": { + "content": answer + }, + + "at": { + "atMobiles": [ + "" + ], + "isAtAll": False + } + } + + timestamp = round(time.time() * 1000) + secret_enc = bytes(self.dingtalk_secret, encoding='utf-8') + string_to_sign = '{}\n{}'.format(timestamp, self.dingtalk_secret) + string_to_sign_enc = bytes(string_to_sign, encoding='utf-8') + hmac_code = hmac.new(secret_enc, string_to_sign_enc, + digestmod=hashlib.sha256).digest() + sign = quote_plus(base64.b64encode(hmac_code)) + + notify_url = f"https://oapi.dingtalk.com/robot/send?access_token={self.dingtalk_token}×tamp={timestamp}&sign={sign}" + try: + r = requests.post(notify_url, json=data) + reply = r.json() + # log.info("[DingTalk] reply={}".format(str(reply))) + except Exception as e: + log.error(e) + + def handle(self, data): + prompt = data['text']['content'] + conversation_id = data['conversationId'] + sender_id = data['senderId'] + context = dict() + img_match_prefix = functions.check_prefix( + prompt, channel_conf_val(const.DINGTALK, 'image_create_prefix')) + if img_match_prefix: + prompt = prompt.split(img_match_prefix, 1)[1].strip() + context['type'] = 'IMAGE_CREATE' + id = sender_id + context['from_user_id'] = str(id) + reply = super().build_reply_content(prompt, context) + if img_match_prefix: + if not isinstance(reply, list): + return reply + images = "" + for url in reply: + images += f"[!['IMAGE_CREATE']({url})]({url})\n" + reply = images + return reply + + +dd = DingTalkChannel() +http_app = Flask(__name__,) + + +@http_app.route("/", methods=['POST']) +def chat(): + # log.info("[DingTalk] chat_headers={}".format(str(request.headers))) + log.info("[DingTalk] chat={}".format(str(request.data))) + token = request.headers.get('token') + if dd.dingtalk_post_token and token != dd.dingtalk_post_token: + return {'ret': 203} + data = json.loads(request.data) + if data: + content = data['text']['content'] + if not content: + return + reply_text = dd.handle(data=data) + dd.notify_dingtalk(reply_text) + return {'ret': 200} + return {'ret': 201} diff --git a/common/const.py b/common/const.py index ad2a061..6324c82 100644 --- a/common/const.py +++ b/common/const.py @@ -8,6 +8,7 @@ GMAIL = "gmail" TELEGRAM = "telegram" SLACK = "slack" HTTP = "http" +DINGTALK = "dingtalk" # model OPEN_AI = "openai" diff --git a/config-template.json b/config-template.json index 5c6fe9e..0c713ec 100644 --- a/config-template.json +++ b/config-template.json @@ -59,6 +59,14 @@ "http_auth_secret_key": "6d25a684-9558-11e9-aa94-efccd7a0659b", "http_auth_password": "6.67428e-11", "port": "80" + }, + + "dingtalk": { + "image_create_prefix": ["画", "draw", "Draw"], + "port": "8081", + "dingtalk_token": "xx", + "dingtalk_post_token": "xx", + "dingtalk_secret": "xx" } }, "common": {