From 1061178f4f0d879a97c0b4f428a14535f697343d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Wed, 24 May 2023 19:26:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0stream=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E7=9A=84=E6=94=AF=E6=8C=81=20(#230)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +- config.example.yml | 2 + config/config.go | 9 ++ docker-compose.yml | 1 + go.mod | 2 + go.sum | 4 + main.go | 342 ++++++++++++++++++++++++---------------- public/example_bot.go | 84 ++++++++++ public/example_event.go | 36 +++++ 9 files changed, 350 insertions(+), 144 deletions(-) create mode 100644 public/example_bot.go create mode 100644 public/example_event.go diff --git a/README.md b/README.md index 9a27d9f4..4a297402 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@

😀企联AI共创计划正式开启😀

- +

https://fork-way.feishu.cn/docx/Gvztd1iVXoXOsVxF2ujcnPPenDf

@@ -46,8 +46,8 @@ 查看更多内容: https://connect-ai.forkway.cn 企业客户咨询:13995928702(River) - - + +

🌉 基于GO语言实现的钉钉集成ChatGPT机器人 🌉

[![Auth](https://img.shields.io/badge/Auth-eryajf-ff69b4)](https://github.com/eryajf) @@ -101,14 +101,14 @@ AIGC的热潮正在各行各业掀起巨大的变革,我们看到各大社群 - A奖励:小队完成度奖励,鼓励小队长参与项目,能够在指定时间内完成课题规定的基本内容,队长应获得一定的奖励。 - B奖励:项目优秀度奖励,根据项目复杂度、组内配合度、产品创意度,以及期中和期末用户体验打分,评选出部分优秀项目的队长和核心队员,并给予相应奖励。 - C奖励:成员活跃度奖励,考虑到设计和测试身份的特殊性,无法单独带领项目。因此,我们将评选出优秀设计师和优秀测试反馈员,以表彰他们在项目中的积极参与和贡献。 - + 做出下面奖励安排 - A奖励项目完成度:京东E卡300 * 10 - B奖励项目优秀度: - 杰出奖: iPhone14 * 1 + 京东E卡300 * 3 - 优秀奖: PS5 * 1 + 京东E卡300 * 3 - C奖励成员活跃度:京东E卡300 * 4 - + 我们队员有 - [EX-chatGPT](https://github.com/circlestarzero/EX-chatGPT)和[ChatPaper的维护者](https://github.com/kaixindelele/ChatPaper)-->[cc](https://github.com/circlestarzero) - [钉钉GPT的维护者](https://github.com/eryajf/chatgpt-dingtalk)-->[eryajf](https://github.com/eryajf) @@ -222,7 +222,7 @@ $ docker run -itd --name chatgpt -p 8090:8090 \ -e DEFAULT_MODE="单聊" -e MAX_REQUEST=0 -e PORT=8090 \ -e SERVICE_URL="你当前服务外网可访问的URL" -e CHAT_TYPE="0" \ -e ALLOW_GROUPS=a,b -e ALLOW_OUTGOING_GROUPS=a,b -e ALLOW_USERS=a,b -e DENY_USERS=a,b -e VIP_USERS=a,b -e ADMIN_USERS=a,b -e APP_SECRETS="xxx,yyy" \ - -e SENSITIVE_WORDS="aa,bb" \ + -e SENSITIVE_WORDS="aa,bb" -e RUN_MODE="http" \ -e AZURE_ON="false" -e AZURE_API_VERSION="" -e AZURE_RESOURCE_NAME="" \ -e AZURE_DEPLOYMENT_NAME="" -e AZURE_OPENAI_TOKEN="" \ -e DINGTALK_CREDENTIALS="your_client_id1:secret1,your_client_id2:secret2" \ @@ -486,6 +486,8 @@ $ go run main.go log_level: "info" # openai api_key api_key: "xxxxxxxxx" +# 运行模式,http 或者 stream ,当前默认为http,等stream全面开放之后,这个模式将会是默认的启动模式 +run_mode: "http" # 如果你使用官方的接口地址 https://api.openai.com,则留空即可,如果你想指定请求url的地址,可通过这个参数进行配置,注意需要带上 http 协议 base_url: "" # 指定模型,默认为 gpt-3.5-turbo , 可选参数有: "gpt-4-0314", "gpt-4", "gpt-3.5-turbo-0301", "gpt-3.5-turbo",如果使用gpt-4,请确认自己是否有接口调用白名单 diff --git a/config.example.yml b/config.example.yml index 565dc845..4b88111c 100644 --- a/config.example.yml +++ b/config.example.yml @@ -2,6 +2,8 @@ log_level: "info" # openai api_key api_key: "xxxxxxxxx" +# 运行模式,http 或者 stream ,当前默认为http,等stream全面开放之后,这个模式将会是默认的启动模式 +run_mode: "http" # 如果你使用官方的接口地址 https://api.openai.com,则留空即可,如果你想指定请求url的地址,可通过这个参数进行配置,注意需要带上 http 协议 base_url: "" # 指定模型,默认为 gpt-3.5-turbo , 可选参数有: "gpt-4-0314", "gpt-4", "gpt-3.5-turbo-0301", "gpt-3.5-turbo",如果使用gpt-4,请确认自己是否有接口调用白名单 diff --git a/config/config.go b/config/config.go index 79d415a1..8aee3fa0 100644 --- a/config/config.go +++ b/config/config.go @@ -25,6 +25,8 @@ type Configuration struct { LogLevel string `yaml:"log_level"` // gpt apikey ApiKey string `yaml:"api_key"` + // 运行模式 + RunMode string `yaml:"run_mode"` // 请求的 URL 地址 BaseURL string `yaml:"base_url"` // 使用模型 @@ -97,6 +99,10 @@ func LoadConfig() *Configuration { if apiKey != "" { config.ApiKey = apiKey } + runMode := os.Getenv("RUN_MODE") + if runMode != "" { + config.RunMode = runMode + } baseURL := os.Getenv("BASE_URL") if baseURL != "" { config.BaseURL = baseURL @@ -216,6 +222,9 @@ func LoadConfig() *Configuration { if config.LogLevel == "" { config.LogLevel = "info" } + if config.RunMode == "" { + config.LogLevel = "http" + } if config.Model == "" { config.Model = "gpt-3.5-turbo" } diff --git a/docker-compose.yml b/docker-compose.yml index 6ecce635..c5647d4a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,7 @@ services: environment: LOG_LEVEL: "info" # 应用的日志级别 info/debug APIKEY: xxxxxx # 你的 api_key + RUN_MODE: "" # 运行模式,http 或者 stream ,当前默认为http,等stream全面开放之后,这个模式将会是默认的启动模式 BASE_URL: "" # 如果你使用官方的接口地址 https://api.openai.com,则留空即可,如果你想指定请求url的地址,可通过这个参数进行配置,注意需要带上 http 协议 MODEL: "gpt-3.5-turbo" # 指定模型,默认为 gpt-3.5-turbo , 可选参数有: "gpt-4-0314", "gpt-4", "gpt-3.5-turbo-0301", "gpt-3.5-turbo",如果使用gpt-4,请确认自己是否有接口调用白名单 SESSION_TIMEOUT: 600 # 会话超时时间,默认600秒,在会话时间内所有发送给机器人的信息会作为上下文 diff --git a/go.mod b/go.mod index d6f378ed..10beacae 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gin-gonic/gin v1.9.0 github.com/glebarez/sqlite v1.7.0 github.com/go-resty/resty/v2 v2.7.0 + github.com/open-dingtalk/dingtalk-stream-sdk-go v0.0.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/sashabaranov/go-openai v1.6.1 github.com/solywsh/chatgpt v0.0.14 @@ -34,6 +35,7 @@ require ( github.com/goccy/go-json v0.10.0 // indirect github.com/google/pprof v0.0.0-20230406165453-00490a63f317 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 3fc2cde0..928b9ee7 100644 --- a/go.sum +++ b/go.sum @@ -65,6 +65,8 @@ github.com/google/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R1 github.com/google/pprof v0.0.0-20230406165453-00490a63f317/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= @@ -99,6 +101,8 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/open-dingtalk/dingtalk-stream-sdk-go v0.0.1 h1:F7c4ZWg5FuL0giOVg0slzPmYLwbuQqTjsu5BkUL6VEU= +github.com/open-dingtalk/dingtalk-stream-sdk-go v0.0.1/go.mod h1:ln3IqPYYocZbYvl9TAOrG/cxGR9xcn4pnZRLdCTEGEU= github.com/pandodao/tokenizer-go v0.2.0 h1:NhfI8fGvQkDld2cZCag6NEU3pJ/ugU9zoY1R/zi9YCs= github.com/pandodao/tokenizer-go v0.2.0/go.mod h1:t6qFbaleKxbv0KNio2XUN/mfGM5WKv4haPXDQWVDG00= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= diff --git a/main.go b/main.go index d5d595bb..84b10294 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,11 @@ import ( "github.com/eryajf/chatgpt-dingtalk/pkg/process" "github.com/eryajf/chatgpt-dingtalk/public" "github.com/gin-gonic/gin" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/client" + loger "github.com/open-dingtalk/dingtalk-stream-sdk-go/logger" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/payload" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/utils" ) func init() { @@ -21,10 +26,77 @@ func init() { logger.InitLogger(public.Config.LogLevel) } func main() { - Start() + if public.Config.RunMode == "http" { + StartHttp() + } else { + for _, credential := range public.Config.Credentials { + StartStream(credential.ClientID, credential.ClientSecret) + } + select {} + } +} + +// 启动为 stream 模式 +func StartStream(clientId, clientSecret string) { + receiver := NewChatReceiver(clientId, clientSecret) + loger.SetLogger(loger.NewStdTestLogger()) + cli := client.NewStreamClient( + client.WithAppCredential(client.NewAppCredentialConfig(clientId, clientSecret)), + client.WithUserAgent(client.NewDingtalkGoSDKUserAgent()), + client.WithSubscription(utils.SubscriptionTypeKCallback, payload.BotMessageCallbackTopic, chatbot.NewDefaultChatBotFrameHandler(receiver.OnChatReceive).OnEventReceived), + ) + err := cli.Start(context.Background()) + if err != nil { + panic(err) + } + + defer cli.Close() + } -func Start() { +type ChatReceiver struct { + clientId string + clientSecret string +} + +func NewChatReceiver(clientId, clientSecret string) *ChatReceiver { + return &ChatReceiver{ + clientId: clientId, + clientSecret: clientSecret, + } +} + +func (r *ChatReceiver) OnChatReceive(ctx context.Context, data *chatbot.BotCallbackDataModel) (err error) { + msgObj := dingbot.ReceiveMsg{ + ConversationID: data.ConversationId, + AtUsers: []struct { + DingtalkID string "json:\"dingtalkId\"" + }{}, + ChatbotUserID: data.ChatbotUserId, + MsgID: data.MsgId, + SenderNick: data.SenderNick, + IsAdmin: data.IsAdmin, + SenderStaffId: data.SenderStaffId, + SessionWebhookExpiredTime: data.SessionWebhookExpiredTime, + CreateAt: data.CreateAt, + ConversationType: data.ConversationType, + SenderID: data.SenderId, + ConversationTitle: data.ConversationTitle, + IsInAtList: data.IsInAtList, + SessionWebhook: data.SessionWebhook, + Text: dingbot.Text(data.Text), + RobotCode: "", + Msgtype: dingbot.MsgType(data.Msgtype), + } + clientId := r.clientId + var c gin.Context + c.Set(public.DingTalkClientIdKeyName, clientId) + DoRequest(msgObj, &c) + + return nil +} + +func StartHttp() { app := gin.Default() app.POST("/", func(c *gin.Context) { var msgObj dingbot.ReceiveMsg @@ -32,142 +104,7 @@ func Start() { if err != nil { return } - // 先校验回调是否合法 - clientId, checkOk := public.CheckRequestWithCredentials(c.GetHeader("timestamp"), c.GetHeader("sign")) - if !checkOk { - logger.Warning("该请求不合法,可能是其他企业或者未经允许的应用调用所致,请知悉!") - return - } - // 通过 context 传递 OAuth ClientID,用于后续流程中调用钉钉OpenAPI - c.Set(public.DingTalkClientIdKeyName, clientId) - // 为了兼容存量老用户,暂时保留 public.CheckRequest 方法,将来升级到 Stream 模式后,建议去除该方法,采用上面的 CheckRequestWithCredentials - if !public.CheckRequest(c.GetHeader("timestamp"), c.GetHeader("sign")) && msgObj.SenderStaffId != "" { - logger.Warning("该请求不合法,可能是其他企业或者未经允许的应用调用所致,请知悉!") - return - } else if !public.JudgeOutgoingGroup(msgObj.ConversationID) && msgObj.SenderStaffId == "" { - logger.Warning("该请求不合法,可能是未经允许的普通群outgoing机器人调用所致,请知悉!") - return - } - // 再校验回调参数是否有价值 - if msgObj.Text.Content == "" || msgObj.ChatbotUserID == "" { - logger.Warning("从钉钉回调过来的内容为空,根据过往的经验,或许重新创建一下机器人,能解决这个问题") - return - } - // 去除问题的前后空格 - msgObj.Text.Content = strings.TrimSpace(msgObj.Text.Content) - if public.JudgeSensitiveWord(msgObj.Text.Content) { - logger.Info(fmt.Sprintf("🙋 %s提问的问题中包含敏感词汇,userid:%#v,消息: %#v", msgObj.SenderNick, msgObj.SenderStaffId, msgObj.Text.Content)) - _, err = msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,您提问的问题中包含敏感词汇,请审核自己的对话内容之后再进行!**") - if err != nil { - logger.Warning(fmt.Errorf("send message error: %v", err)) - return - } - return - } - // 打印钉钉回调过来的请求明细,调试时打开 - logger.Debug(fmt.Sprintf("dingtalk callback parameters: %#v", msgObj)) - - if public.Config.ChatType != "0" && msgObj.ConversationType != public.Config.ChatType { - logger.Info(fmt.Sprintf("🙋 %s使用了禁用的聊天方式", msgObj.SenderNick)) - _, err = msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,管理员禁用了这种聊天方式,请选择其他聊天方式与机器人对话!**") - if err != nil { - logger.Warning(fmt.Errorf("send message error: %v", err)) - return - } - return - } - - // 查询群ID,发送指令后,可通过查看日志来获取 - if msgObj.ConversationType == "2" && msgObj.Text.Content == "群ID" { - if msgObj.RobotCode == "normal" { - logger.Info(fmt.Sprintf("🙋 outgoing机器人 在『%s』群的ConversationID为: %#v", msgObj.ConversationTitle, msgObj.ConversationID)) - } else { - logger.Info(fmt.Sprintf("🙋 企业内部机器人 在『%s』群的ConversationID为: %#v", msgObj.ConversationTitle, msgObj.ConversationID)) - } - //_, err = msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), msgObj.ConversationID) - if err != nil { - logger.Warning(fmt.Errorf("send message error: %v", err)) - return - } - return - } - - // 不在允许群组,不在允许用户(包括在黑名单),满足任一条件,拒绝会话;管理员不受限制 - if msgObj.ConversationType == "2" && !public.JudgeGroup(msgObj.ConversationID) && !public.JudgeAdminUsers(msgObj.SenderStaffId) && msgObj.SenderStaffId != "" { - logger.Info(fmt.Sprintf("🙋『%s』群组未被验证通过,群ID: %#v,userid:%#v, 昵称: %#v,消息: %#v", msgObj.ConversationTitle, msgObj.ConversationID, msgObj.SenderStaffId, msgObj.SenderNick, msgObj.Text.Content)) - _, err = msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,该群组未被认证通过,无法使用机器人对话功能。**\n>如需继续使用,请联系管理员申请访问权限。") - if err != nil { - logger.Warning(fmt.Errorf("send message error: %v", err)) - return - } - return - } else if !public.JudgeUsers(msgObj.SenderStaffId) && !public.JudgeAdminUsers(msgObj.SenderStaffId) && msgObj.SenderStaffId != "" { - logger.Info(fmt.Sprintf("🙋 %s身份信息未被验证通过,userid:%#v,消息: %#v", msgObj.SenderNick, msgObj.SenderStaffId, msgObj.Text.Content)) - _, err = msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,您的身份信息未被认证通过,无法使用机器人对话功能。**\n>如需继续使用,请联系管理员申请访问权限。") - if err != nil { - logger.Warning(fmt.Errorf("send message error: %v", err)) - return - } - return - } - if len(msgObj.Text.Content) == 0 || msgObj.Text.Content == "帮助" { - // 欢迎信息 - _, err := msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), public.Config.Help) - if err != nil { - logger.Warning(fmt.Errorf("send message error: %v", err)) - return - } - } else { - logger.Info(fmt.Sprintf("🙋 %s发起的问题: %#v", msgObj.SenderNick, msgObj.Text.Content)) - // 除去帮助之外的逻辑分流在这里处理 - switch { - case strings.HasPrefix(msgObj.Text.Content, "#图片"): - err := process.ImageGenerate(c, &msgObj) - if err != nil { - logger.Warning(fmt.Errorf("process request: %v", err)) - return - } - return - case strings.HasPrefix(msgObj.Text.Content, "#查对话"): - err := process.SelectHistory(&msgObj) - if err != nil { - logger.Warning(fmt.Errorf("process request: %v", err)) - return - } - return - case strings.HasPrefix(msgObj.Text.Content, "#域名"): - err := process.DomainMsg(&msgObj) - if err != nil { - logger.Warning(fmt.Errorf("process request: %v", err)) - return - } - return - case strings.HasPrefix(msgObj.Text.Content, "#证书"): - err := process.DomainCertMsg(&msgObj) - if err != nil { - logger.Warning(fmt.Errorf("process request: %v", err)) - return - } - return - default: - msgObj.Text.Content, err = process.GeneratePrompt(msgObj.Text.Content) - // err不为空:提示词之后没有文本 -> 直接返回提示词所代表的内容 - if err != nil { - _, err = msgObj.ReplyToDingtalk(string(dingbot.TEXT), msgObj.Text.Content) - if err != nil { - logger.Warning(fmt.Errorf("send message error: %v", err)) - return - } - return - } - err := process.ProcessRequest(&msgObj) - if err != nil { - logger.Warning(fmt.Errorf("process request: %v", err)) - return - } - return - } - } + DoRequest(msgObj, c) }) // 解析生成后的图片 app.GET("/images/:filename", func(c *gin.Context) { @@ -227,3 +164,132 @@ func Start() { } logger.Info("Server exiting!") } + +func DoRequest(msgObj dingbot.ReceiveMsg, c *gin.Context) { + // 先校验回调是否合法 + if public.Config.RunMode == "http" { + clientId, checkOk := public.CheckRequestWithCredentials(c.GetHeader("timestamp"), c.GetHeader("sign")) + if !checkOk { + logger.Warning("该请求不合法,可能是其他企业或者未经允许的应用调用所致,请知悉!") + return + } + // 通过 context 传递 OAuth ClientID,用于后续流程中调用钉钉OpenAPI + c.Set(public.DingTalkClientIdKeyName, clientId) + } + // 再校验回调参数是否有价值 + if msgObj.Text.Content == "" || msgObj.ChatbotUserID == "" { + logger.Warning("从钉钉回调过来的内容为空,根据过往的经验,或许重新创建一下机器人,能解决这个问题") + return + } + // 去除问题的前后空格 + msgObj.Text.Content = strings.TrimSpace(msgObj.Text.Content) + if public.JudgeSensitiveWord(msgObj.Text.Content) { + logger.Info(fmt.Sprintf("🙋 %s提问的问题中包含敏感词汇,userid:%#v,消息: %#v", msgObj.SenderNick, msgObj.SenderStaffId, msgObj.Text.Content)) + _, err := msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,您提问的问题中包含敏感词汇,请审核自己的对话内容之后再进行!**") + if err != nil { + logger.Warning(fmt.Errorf("send message error: %v", err)) + return + } + return + } + // 打印钉钉回调过来的请求明细,调试时打开 + logger.Debug(fmt.Sprintf("dingtalk callback parameters: %#v", msgObj)) + + if public.Config.ChatType != "0" && msgObj.ConversationType != public.Config.ChatType { + logger.Info(fmt.Sprintf("🙋 %s使用了禁用的聊天方式", msgObj.SenderNick)) + _, err := msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,管理员禁用了这种聊天方式,请选择其他聊天方式与机器人对话!**") + if err != nil { + logger.Warning(fmt.Errorf("send message error: %v", err)) + return + } + return + } + + // 查询群ID,发送指令后,可通过查看日志来获取 + if msgObj.ConversationType == "2" && msgObj.Text.Content == "群ID" { + if msgObj.RobotCode == "normal" { + logger.Info(fmt.Sprintf("🙋 outgoing机器人 在『%s』群的ConversationID为: %#v", msgObj.ConversationTitle, msgObj.ConversationID)) + } else { + logger.Info(fmt.Sprintf("🙋 企业内部机器人 在『%s』群的ConversationID为: %#v", msgObj.ConversationTitle, msgObj.ConversationID)) + } + return + } + + // 不在允许群组,不在允许用户(包括在黑名单),满足任一条件,拒绝会话;管理员不受限制 + if msgObj.ConversationType == "2" && !public.JudgeGroup(msgObj.ConversationID) && !public.JudgeAdminUsers(msgObj.SenderStaffId) && msgObj.SenderStaffId != "" { + logger.Info(fmt.Sprintf("🙋『%s』群组未被验证通过,群ID: %#v,userid:%#v, 昵称: %#v,消息: %#v", msgObj.ConversationTitle, msgObj.ConversationID, msgObj.SenderStaffId, msgObj.SenderNick, msgObj.Text.Content)) + _, err := msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,该群组未被认证通过,无法使用机器人对话功能。**\n>如需继续使用,请联系管理员申请访问权限。") + if err != nil { + logger.Warning(fmt.Errorf("send message error: %v", err)) + return + } + return + } else if !public.JudgeUsers(msgObj.SenderStaffId) && !public.JudgeAdminUsers(msgObj.SenderStaffId) && msgObj.SenderStaffId != "" { + logger.Info(fmt.Sprintf("🙋 %s身份信息未被验证通过,userid:%#v,消息: %#v", msgObj.SenderNick, msgObj.SenderStaffId, msgObj.Text.Content)) + _, err := msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,您的身份信息未被认证通过,无法使用机器人对话功能。**\n>如需继续使用,请联系管理员申请访问权限。") + if err != nil { + logger.Warning(fmt.Errorf("send message error: %v", err)) + return + } + return + } + if len(msgObj.Text.Content) == 0 || msgObj.Text.Content == "帮助" { + // 欢迎信息 + _, err := msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), public.Config.Help) + if err != nil { + logger.Warning(fmt.Errorf("send message error: %v", err)) + return + } + } else { + logger.Info(fmt.Sprintf("🙋 %s发起的问题: %#v", msgObj.SenderNick, msgObj.Text.Content)) + // 除去帮助之外的逻辑分流在这里处理 + switch { + case strings.HasPrefix(msgObj.Text.Content, "#图片"): + err := process.ImageGenerate(c, &msgObj) + if err != nil { + logger.Warning(fmt.Errorf("process request: %v", err)) + return + } + return + case strings.HasPrefix(msgObj.Text.Content, "#查对话"): + err := process.SelectHistory(&msgObj) + if err != nil { + logger.Warning(fmt.Errorf("process request: %v", err)) + return + } + return + case strings.HasPrefix(msgObj.Text.Content, "#域名"): + err := process.DomainMsg(&msgObj) + if err != nil { + logger.Warning(fmt.Errorf("process request: %v", err)) + return + } + return + case strings.HasPrefix(msgObj.Text.Content, "#证书"): + err := process.DomainCertMsg(&msgObj) + if err != nil { + logger.Warning(fmt.Errorf("process request: %v", err)) + return + } + return + default: + var err error + msgObj.Text.Content, err = process.GeneratePrompt(msgObj.Text.Content) + // err不为空:提示词之后没有文本 -> 直接返回提示词所代表的内容 + if err != nil { + _, err = msgObj.ReplyToDingtalk(string(dingbot.TEXT), msgObj.Text.Content) + if err != nil { + logger.Warning(fmt.Errorf("send message error: %v", err)) + return + } + return + } + err = process.ProcessRequest(&msgObj) + if err != nil { + logger.Warning(fmt.Errorf("process request: %v", err)) + return + } + return + } + } +} diff --git a/public/example_bot.go b/public/example_bot.go new file mode 100644 index 00000000..274ef9f1 --- /dev/null +++ b/public/example_bot.go @@ -0,0 +1,84 @@ +package public + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/client" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/logger" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/payload" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/utils" +) + +/** + * @Author linya.jj + * @Date 2023/3/22 18:30 + */ + +func OnBotCallback(ctx context.Context, df *payload.DataFrame) (*payload.DataFrameResponse, error) { + frameResp := &payload.DataFrameResponse{ + Code: 200, + Headers: payload.DataFrameHeader{ + payload.DataFrameHeaderKContentType: payload.DataFrameContentTypeKJson, + payload.DataFrameHeaderKMessageId: df.GetMessageId(), + }, + Message: "ok", + Data: "", + } + + return frameResp, nil +} + +func OnChatReceive(ctx context.Context, data *chatbot.BotCallbackDataModel) error { + requestBody := map[string]interface{}{ + "msgtype": "text", + "text": map[string]interface{}{ + "content": fmt.Sprintf("msg received: [%s]", data.Text.Content), + }, + } + + requestJsonBody, _ := json.Marshal(requestBody) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, data.SessionWebhook, bytes.NewReader(requestJsonBody)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "*/*") + + httpClient := &http.Client{ + Transport: http.DefaultTransport, + Timeout: 5 * time.Second, //设置超时,包含connection时间、任意重定向时间、读取response body时间 + } + + _, err = httpClient.Do(req) + if err != nil { + return err + } + + return nil +} + +func RunBotListener(clientId, clientSecret string) { + logger.SetLogger(logger.NewStdTestLogger()) + + cli := client.NewStreamClient( + client.WithAppCredential(client.NewAppCredentialConfig(clientId, clientSecret)), + client.WithUserAgent(client.NewDingtalkGoSDKUserAgent()), + client.WithSubscription(utils.SubscriptionTypeKCallback, payload.BotMessageCallbackTopic, chatbot.NewDefaultChatBotFrameHandler(OnChatReceive).OnEventReceived), + ) + + err := cli.Start(context.Background()) + if err != nil { + panic(err) + } + + defer cli.Close() + + select {} +} diff --git a/public/example_event.go b/public/example_event.go new file mode 100644 index 00000000..a6c328c3 --- /dev/null +++ b/public/example_event.go @@ -0,0 +1,36 @@ +package public + +import ( + "context" + + "github.com/open-dingtalk/dingtalk-stream-sdk-go/client" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/event" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/logger" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/utils" +) + +/** + * @Author linya.jj + * @Date 2023/3/22 18:30 + */ + +func RunEventListener(clientId, clientSecret string) { + logger.SetLogger(logger.NewStdTestLogger()) + + eventHandler := event.NewDefaultEventFrameHandler(event.EventHandlerDoNothing) + + cli := client.NewStreamClient( + client.WithAppCredential(client.NewAppCredentialConfig(clientId, clientSecret)), + client.WithUserAgent(client.NewDingtalkGoSDKUserAgent()), + client.WithSubscription(utils.SubscriptionTypeKEvent, "*", eventHandler.OnEventReceived), + ) + + err := cli.Start(context.Background()) + if err != nil { + panic(err) + } + + defer cli.Close() + + select {} +}