From f6bee3aa58f0f67b22a33e568702a844e4f1e7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=83=A0=E6=96=87?= Date: Thu, 30 Nov 2023 10:41:34 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=92=89=E9=92=89?= =?UTF-8?q?=E6=9C=BA=E5=99=A8=E4=BA=BA=EF=BC=88Stream=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 2 +- channel/channel_factory.py | 3 + channel/dingtalk/dingtalk_channel.py | 151 +++++++++++++++++++++++++++ channel/dingtalk/dingtalk_message.py | 39 +++++++ common/const.py | 1 + config.py | 6 +- 6 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 channel/dingtalk/dingtalk_channel.py create mode 100644 channel/dingtalk/dingtalk_message.py diff --git a/app.py b/app.py index 19acdcd..2a14cba 100644 --- a/app.py +++ b/app.py @@ -43,7 +43,7 @@ def run(): # os.environ['WECHATY_PUPPET_SERVICE_ENDPOINT'] = '127.0.0.1:9001' channel = channel_factory.create_channel(channel_name) - if channel_name in ["wx", "wxy", "terminal", "wechatmp", "wechatmp_service", "wechatcom_app", "wework", const.FEISHU]: + if channel_name in ["wx", "wxy", "terminal", "wechatmp", "wechatmp_service", "wechatcom_app", "wework", const.FEISHU,const.DINGTALK]: PluginManager().load_plugins() # startup channel diff --git a/channel/channel_factory.py b/channel/channel_factory.py index 7044b9a..71f5f37 100644 --- a/channel/channel_factory.py +++ b/channel/channel_factory.py @@ -40,5 +40,8 @@ def create_channel(channel_type): elif channel_type == const.FEISHU: from channel.feishu.feishu_channel import FeiShuChanel return FeiShuChanel() + elif channel_type == const.DINGTALK: + from channel.dingtalk.dingtalk_channel import DingTalkChanel + return DingTalkChanel() raise RuntimeError diff --git a/channel/dingtalk/dingtalk_channel.py b/channel/dingtalk/dingtalk_channel.py new file mode 100644 index 0000000..9a9b170 --- /dev/null +++ b/channel/dingtalk/dingtalk_channel.py @@ -0,0 +1,151 @@ +""" +钉钉通道接入 + +@author huiwen +@Date 2023/11/28 +""" + +# -*- coding=utf-8 -*- +import uuid + +import requests +import web +from channel.dingtalk.dingtalk_message import DingTalkMessage +from bridge.context import Context +from bridge.reply import Reply, ReplyType +from common.log import logger +from common.singleton import singleton +from config import conf +from common.expired_dict import ExpiredDict +from bridge.context import ContextType +from channel.chat_channel import ChatChannel, check_prefix +from common import utils +import json +import os + + + +import argparse +import logging +from dingtalk_stream import AckMessage +import dingtalk_stream + +@singleton +class DingTalkChanel(ChatChannel,dingtalk_stream.ChatbotHandler): + dingtalk_app_id = conf().get('dingtalk_app_id') + dingtalk_app_secret = conf().get('dingtalk_app_secret') + + def setup_logger(self): + logger = logging.getLogger() + handler = logging.StreamHandler() + handler.setFormatter( + logging.Formatter('%(asctime)s %(name)-8s %(levelname)-8s %(message)s [%(filename)s:%(lineno)d]')) + logger.addHandler(handler) + logger.setLevel(logging.INFO) + return logger + def __init__(self): + super().__init__() + super(dingtalk_stream.ChatbotHandler, self).__init__() + + self.logger = self.setup_logger() + # 历史消息id暂存,用于幂等控制 + self.receivedMsgs = ExpiredDict(60 * 60 * 7.1) + + logger.info("[dingtalk] app_id={}, app_secret={} ".format( + self.dingtalk_app_id, self.dingtalk_app_secret)) + # 无需群校验和前缀 + conf()["group_name_white_list"] = ["ALL_GROUP"] + conf()["single_chat_prefix"] = [] + + def startup(self): + + credential = dingtalk_stream.Credential( self.dingtalk_app_id, self.dingtalk_app_secret) + client = dingtalk_stream.DingTalkStreamClient(credential) + client.register_callback_handler(dingtalk_stream.chatbot.ChatbotMessage.TOPIC,self) + client.start_forever() + + def handle_single(self, cmsg:DingTalkMessage): + # 处理单聊消息 + # + + if cmsg.ctype == ContextType.VOICE: + + logger.debug("[dingtalk]receive voice msg: {}".format(cmsg.content)) + elif cmsg.ctype == ContextType.IMAGE: + logger.debug("[dingtalk]receive image msg: {}".format(cmsg.content)) + elif cmsg.ctype == ContextType.PATPAT: + logger.debug("[dingtalk]receive patpat msg: {}".format(cmsg.content)) + elif cmsg.ctype == ContextType.TEXT: + expression = cmsg.my_msg + + + context = self._compose_context(cmsg.ctype, cmsg.content, isgroup=False, msg=cmsg) + if context: + self.produce(context) + + + + async def process(self, callback: dingtalk_stream.CallbackMessage): + + + + try: + + incoming_message = dingtalk_stream.ChatbotMessage.from_dict(callback.data) + dingtalk_msg = DingTalkMessage(incoming_message) + if incoming_message.conversation_type == '1': + self.handle_single(dingtalk_msg) + else: + self.handle_single(dingtalk_msg) + + except Exception as e: + logger.error(e) + return self.FAILED_MSG + + + def send(self, reply: Reply, context: Context): + + + incoming_message = context.kwargs['msg'].incoming_message + self.reply_text(reply.content, incoming_message) + + return AckMessage.STATUS_OK, 'OK' + +class dingtalkController: + # 类常量 + FAILED_MSG = '{"success": false}' + SUCCESS_MSG = '{"success": true}' + MESSAGE_RECEIVE_TYPE = "im.message.receive_v1" + + def GET(self): + return "dingtalk service start success!" + + + + def _compose_context(self, ctype: ContextType, content, **kwargs): + context = Context(ctype, content) + context.kwargs = kwargs + if "origin_ctype" not in context: + context["origin_ctype"] = ctype + + cmsg = context["msg"] + context["session_id"] = cmsg.from_user_id + context["receiver"] = cmsg.other_user_id + + if ctype == ContextType.TEXT: + # 1.文本请求 + # 图片生成处理 + img_match_prefix = check_prefix(content, conf().get("image_create_prefix")) + if img_match_prefix: + content = content.replace(img_match_prefix, "", 1) + context.type = ContextType.IMAGE_CREATE + else: + context.type = ContextType.TEXT + context.content = content.strip() + + elif context.type == ContextType.VOICE: + # 2.语音请求 + if "desire_rtype" not in context and conf().get("voice_reply_voice"): + context["desire_rtype"] = ReplyType.VOICE + + return context diff --git a/channel/dingtalk/dingtalk_message.py b/channel/dingtalk/dingtalk_message.py new file mode 100644 index 0000000..06dea41 --- /dev/null +++ b/channel/dingtalk/dingtalk_message.py @@ -0,0 +1,39 @@ +from bridge.context import ContextType +from channel.chat_message import ChatMessage +import json +import requests +from common.log import logger +from common.tmp_dir import TmpDir +from common import utils +from dingtalk_stream import ChatbotMessage + +class DingTalkMessage(ChatMessage): + def __init__(self, event: ChatbotMessage): + super().__init__(event) + + self.msg_id = event.message_id + msg_type = event.message_type + self.incoming_message =event + self.sender_staff_id = event.sender_staff_id + + self.create_time = event.create_at + if event.conversation_type=="1": + self.is_group = False + else: + self.is_group = True + + + if msg_type == "text": + self.ctype = ContextType.TEXT + + self.content = event.text.content.strip() + + self.from_user_id = event.sender_id + self.to_user_id = event.chatbot_user_id + + user_id = event.sender_id + nickname =event.sender_nick + + + + diff --git a/common/const.py b/common/const.py index b8d701a..6f3589c 100644 --- a/common/const.py +++ b/common/const.py @@ -20,3 +20,4 @@ MODEL_LIST = ["gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-4", "wenxin", "wenxin-4 # channel FEISHU = "feishu" +DINGTALK = "dingtalk" diff --git a/config.py b/config.py index 6cc5708..25deb04 100644 --- a/config.py +++ b/config.py @@ -123,7 +123,11 @@ available_setting = { "feishu_app_secret": "", # 飞书机器人APP secret "feishu_token": "", # 飞书 verification token "feishu_bot_name": "", # 飞书机器人的名字 - + + # 钉钉配置 + "dingtalk_app_id": "", # 钉钉机器人应用APP Id + "dingtalk_app_secret": "", # 钉钉机器人APP secret + # chatgpt指令自定义触发词 "clear_memory_commands": ["#清除记忆"], # 重置会话指令,必须以#开头 # channel配置 From 32a8a847fc5f892779d03a220fbb43ca36c0c0b1 Mon Sep 17 00:00:00 2001 From: huiwen Date: Thu, 30 Nov 2023 12:09:03 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B0=8Fbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- channel/dingtalk/dingtalk_channel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/channel/dingtalk/dingtalk_channel.py b/channel/dingtalk/dingtalk_channel.py index 9a9b170..ad95883 100644 --- a/channel/dingtalk/dingtalk_channel.py +++ b/channel/dingtalk/dingtalk_channel.py @@ -97,7 +97,7 @@ class DingTalkChanel(ChatChannel,dingtalk_stream.ChatbotHandler): self.handle_single(dingtalk_msg) else: self.handle_single(dingtalk_msg) - + return AckMessage.STATUS_OK, 'OK' except Exception as e: logger.error(e) return self.FAILED_MSG @@ -109,7 +109,7 @@ class DingTalkChanel(ChatChannel,dingtalk_stream.ChatbotHandler): incoming_message = context.kwargs['msg'].incoming_message self.reply_text(reply.content, incoming_message) - return AckMessage.STATUS_OK, 'OK' + class dingtalkController: # 类常量 From a21dd97786bcef834462cfd73a4742d8fba92831 Mon Sep 17 00:00:00 2001 From: huiwen Date: Sun, 17 Dec 2023 09:23:15 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E9=92=89=E9=92=89app=5Fid,=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E4=B8=BA=5Fclient=5Fid=EF=BC=8C=E5=92=8C=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- channel/dingtalk/dingtalk_channel.py | 95 ++++++++++++++++------------ channel/dingtalk/dingtalk_message.py | 3 +- config.py | 4 +- 3 files changed, 58 insertions(+), 44 deletions(-) diff --git a/channel/dingtalk/dingtalk_channel.py b/channel/dingtalk/dingtalk_channel.py index ad95883..1f86ce7 100644 --- a/channel/dingtalk/dingtalk_channel.py +++ b/channel/dingtalk/dingtalk_channel.py @@ -32,8 +32,8 @@ import dingtalk_stream @singleton class DingTalkChanel(ChatChannel,dingtalk_stream.ChatbotHandler): - dingtalk_app_id = conf().get('dingtalk_app_id') - dingtalk_app_secret = conf().get('dingtalk_app_secret') + dingtalk_client_id = conf().get('dingtalk_client_id') + dingtalk_client_secret = conf().get('dingtalk_client_secret') def setup_logger(self): logger = logging.getLogger() @@ -51,15 +51,16 @@ class DingTalkChanel(ChatChannel,dingtalk_stream.ChatbotHandler): # 历史消息id暂存,用于幂等控制 self.receivedMsgs = ExpiredDict(60 * 60 * 7.1) - logger.info("[dingtalk] app_id={}, app_secret={} ".format( - self.dingtalk_app_id, self.dingtalk_app_secret)) + logger.info("[dingtalk] client_id={}, client_secret={} ".format( + self.dingtalk_client_id, self.dingtalk_client_secret)) # 无需群校验和前缀 conf()["group_name_white_list"] = ["ALL_GROUP"] - conf()["single_chat_prefix"] = [] + + def startup(self): - credential = dingtalk_stream.Credential( self.dingtalk_app_id, self.dingtalk_app_secret) + credential = dingtalk_stream.Credential( self.dingtalk_client_id, self.dingtalk_client_secret) client = dingtalk_stream.DingTalkStreamClient(credential) client.register_callback_handler(dingtalk_stream.chatbot.ChatbotMessage.TOPIC,self) client.start_forever() @@ -78,11 +79,32 @@ class DingTalkChanel(ChatChannel,dingtalk_stream.ChatbotHandler): elif cmsg.ctype == ContextType.TEXT: expression = cmsg.my_msg - + cmsg.content = conf()["single_chat_prefix"][0] + cmsg.content + context = self._compose_context(cmsg.ctype, cmsg.content, isgroup=False, msg=cmsg) + if context: self.produce(context) + def handle_group(self, cmsg:DingTalkMessage): + # 处理群聊消息 + # + + if cmsg.ctype == ContextType.VOICE: + + logger.debug("[dingtalk]receive voice msg: {}".format(cmsg.content)) + elif cmsg.ctype == ContextType.IMAGE: + logger.debug("[dingtalk]receive image msg: {}".format(cmsg.content)) + elif cmsg.ctype == ContextType.PATPAT: + logger.debug("[dingtalk]receive patpat msg: {}".format(cmsg.content)) + elif cmsg.ctype == ContextType.TEXT: + expression = cmsg.my_msg + + cmsg.content = conf()["group_chat_prefix"][0] + cmsg.content + context = self._compose_context(cmsg.ctype, cmsg.content, isgroup=True, msg=cmsg) + context['no_need_at']=True + if context: + self.produce(context) async def process(self, callback: dingtalk_stream.CallbackMessage): @@ -96,7 +118,7 @@ class DingTalkChanel(ChatChannel,dingtalk_stream.ChatbotHandler): if incoming_message.conversation_type == '1': self.handle_single(dingtalk_msg) else: - self.handle_single(dingtalk_msg) + self.handle_group(dingtalk_msg) return AckMessage.STATUS_OK, 'OK' except Exception as e: logger.error(e) @@ -111,41 +133,32 @@ class DingTalkChanel(ChatChannel,dingtalk_stream.ChatbotHandler): -class dingtalkController: - # 类常量 - FAILED_MSG = '{"success": false}' - SUCCESS_MSG = '{"success": true}' - MESSAGE_RECEIVE_TYPE = "im.message.receive_v1" - def GET(self): - return "dingtalk service start success!" - + # def _compose_context(self, ctype: ContextType, content, **kwargs): + # context = Context(ctype, content) + # context.kwargs = kwargs + # if "origin_ctype" not in context: + # context["origin_ctype"] = ctype - def _compose_context(self, ctype: ContextType, content, **kwargs): - context = Context(ctype, content) - context.kwargs = kwargs - if "origin_ctype" not in context: - context["origin_ctype"] = ctype - - cmsg = context["msg"] - context["session_id"] = cmsg.from_user_id - context["receiver"] = cmsg.other_user_id - - if ctype == ContextType.TEXT: - # 1.文本请求 - # 图片生成处理 - img_match_prefix = check_prefix(content, conf().get("image_create_prefix")) - if img_match_prefix: - content = content.replace(img_match_prefix, "", 1) - context.type = ContextType.IMAGE_CREATE - else: - context.type = ContextType.TEXT - context.content = content.strip() + # cmsg = context["msg"] + # context["session_id"] = cmsg.from_user_id + # context["receiver"] = cmsg.other_user_id + + # if ctype == ContextType.TEXT: + # # 1.文本请求 + # # 图片生成处理 + # img_match_prefix = check_prefix(content, conf().get("image_create_prefix")) + # if img_match_prefix: + # content = content.replace(img_match_prefix, "", 1) + # context.type = ContextType.IMAGE_CREATE + # else: + # context.type = ContextType.TEXT + # context.content = content.strip() - elif context.type == ContextType.VOICE: - # 2.语音请求 - if "desire_rtype" not in context and conf().get("voice_reply_voice"): - context["desire_rtype"] = ReplyType.VOICE + # elif context.type == ContextType.VOICE: + # # 2.语音请求 + # if "desire_rtype" not in context and conf().get("voice_reply_voice"): + # context["desire_rtype"] = ReplyType.VOICE - return context + # return context diff --git a/channel/dingtalk/dingtalk_message.py b/channel/dingtalk/dingtalk_message.py index 06dea41..57e1455 100644 --- a/channel/dingtalk/dingtalk_message.py +++ b/channel/dingtalk/dingtalk_message.py @@ -30,7 +30,8 @@ class DingTalkMessage(ChatMessage): self.from_user_id = event.sender_id self.to_user_id = event.chatbot_user_id - + self.other_user_nickname = event.conversation_title + user_id = event.sender_id nickname =event.sender_nick diff --git a/config.py b/config.py index 25deb04..03b5011 100644 --- a/config.py +++ b/config.py @@ -125,8 +125,8 @@ available_setting = { "feishu_bot_name": "", # 飞书机器人的名字 # 钉钉配置 - "dingtalk_app_id": "", # 钉钉机器人应用APP Id - "dingtalk_app_secret": "", # 钉钉机器人APP secret + "dingtalk_client_id": "", # 钉钉机器人Client ID + "dingtalk_client_secret": "", # 钉钉机器人Client Secret # chatgpt指令自定义触发词 "clear_memory_commands": ["#清除记忆"], # 重置会话指令,必须以#开头