diff --git a/README.md b/README.md index 424752e..4e05de2 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ - [使用前提](#%E4%BD%BF%E7%94%A8%E5%89%8D%E6%8F%90) - [使用教程](#%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B) - [第一步,创建机器人](#%E7%AC%AC%E4%B8%80%E6%AD%A5%E5%88%9B%E5%BB%BA%E6%9C%BA%E5%99%A8%E4%BA%BA) + - [方案一:outgoing类型机器人](#%E6%96%B9%E6%A1%88%E4%B8%80outgoing%E7%B1%BB%E5%9E%8B%E6%9C%BA%E5%99%A8%E4%BA%BA) + - [方案二:企业内部应用](#%E6%96%B9%E6%A1%88%E4%BA%8C%E4%BC%81%E4%B8%9A%E5%86%85%E9%83%A8%E5%BA%94%E7%94%A8) - [第二步,部署应用](#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E9%83%A8%E7%BD%B2%E5%BA%94%E7%94%A8) - [docker部署](#docker%E9%83%A8%E7%BD%B2) - [二进制部署](#%E4%BA%8C%E8%BF%9B%E5%88%B6%E9%83%A8%E7%BD%B2) @@ -85,6 +87,24 @@ ### 第一步,创建机器人 +#### 方案一:outgoing类型机器人 + +钉钉群内的机器人有一个outgoing模式,当你创建机器人的时候,可以选择启用这个模式,然后直接配置回调地址,免去在管理后台创建应用的步骤,就可以直接投入使用。 + +官方文档:[自定义机器人接入](https://open.dingtalk.com/document/orgapp/custom-robot-access) + +但是这个模式貌似是部分开放的(目前来看貌似是部分人有创建这个类型的白名单),所以如果你在钉钉群聊中添加`自定义机器人`的时候,看到和我一样的信息,则说明无法使用这种方式: + +![image_20230325_162017](https://cdn.staticaly.com/gh/eryajf/tu/main/img/image_20230325_162017.jpg) + +`📢 注意` + +- 如果你的和我一样,那么就只能放弃这种方案,往下看第二种对接方案。 +- 如果使用这种方案,那么就不能与机器人私聊对话,只能局限在群聊当中艾特机器人聊天。 +- 如果使用这种方案,则在群聊当中并不能达到真正的艾特发消息人的效果,因为这种机器人回调过来的关键信息为空。 + +#### 方案二:企业内部应用 + 创建步骤参考文档:[企业内部开发机器人](https://open.dingtalk.com/document/robots/enterprise-created-chatbot),或者根据如下步骤进行配置。 1. 创建机器人。 diff --git a/main.go b/main.go index 6cc4a6c..9db20ce 100644 --- a/main.go +++ b/main.go @@ -44,12 +44,13 @@ func Start() { logger.Warning("从钉钉回调过来的内容为空,根据过往的经验,或许重新创建一下机器人,能解决这个问题") return ship.ErrBadRequest.New(fmt.Errorf("从钉钉回调过来的内容为空,根据过往的经验,或许重新创建一下机器人,能解决这个问题")) } + // 打印钉钉回调过来的请求明细 logger.Info(fmt.Sprintf("dingtalk callback parameters: %#v", msgObj)) // TODO: 校验请求 if len(msgObj.Text.Content) == 1 || strings.TrimSpace(msgObj.Text.Content) == "帮助" { // 欢迎信息 - _, err := msgObj.ReplyToDingtalk(string(public.TEXT), Welcome, msgObj.SenderStaffId) + _, err := msgObj.ReplyToDingtalk(string(public.MARKDOWN), Welcome) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return ship.ErrBadRequest.New(fmt.Errorf("send message error: %v", err)) diff --git a/pkg/process/process_request.go b/pkg/process/process_request.go index f002e67..ee4e6ad 100644 --- a/pkg/process/process_request.go +++ b/pkg/process/process_request.go @@ -17,20 +17,20 @@ func ProcessRequest(rmsg *public.ReceiveMsg) error { switch content { case "单聊": public.UserService.SetUserMode(rmsg.SenderStaffId, content) - _, err := rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("=====现在进入与👉%s👈单聊的模式 =====", rmsg.SenderNick), rmsg.SenderStaffId) + _, err := rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("=====现在进入与👉%s👈单聊的模式 =====", rmsg.SenderNick)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) } case "串聊": public.UserService.SetUserMode(rmsg.SenderStaffId, content) - _, err := rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("=====现在进入与👉%s👈串聊的模式 =====", rmsg.SenderNick), rmsg.SenderStaffId) + _, err := rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("=====现在进入与👉%s👈串聊的模式 =====", rmsg.SenderNick)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) } case "重置": public.UserService.ClearUserMode(rmsg.SenderStaffId) public.UserService.ClearUserSessionContext(rmsg.SenderStaffId) - _, err := rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("=====已重置与👉%s👈的对话模式,可以开始新的对话=====", rmsg.SenderNick), rmsg.SenderStaffId) + _, err := rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("=====已重置与👉%s👈的对话模式,可以开始新的对话=====", rmsg.SenderNick)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) } @@ -39,12 +39,12 @@ func ProcessRequest(rmsg *public.ReceiveMsg) error { for _, v := range *public.Prompt { title = title + v.Title + " | " } - _, err := rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("%s 您好,当前程序内置集成了这些prompt:\n====================================\n| %s \n====================================\n你可以选择某个prompt开头,然后进行对话。\n以周报为例,可发送 #周报 我本周用Go写了一个钉钉集成ChatGPT的聊天应用", rmsg.SenderNick, title), rmsg.SenderStaffId) + _, err := rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("%s 您好,当前程序内置集成了这些prompt:\n====================================\n| %s \n====================================\n你可以选择某个prompt开头,然后进行对话。\n以周报为例,可发送 #周报 我本周用Go写了一个钉钉集成ChatGPT的聊天应用", rmsg.SenderNick, title)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) } case "图片": - _, err := rmsg.ReplyToDingtalk(string(public.MARKDOWN), "发送以 **#图片** 开头的内容,将会触发绘画能力,图片生成之后,将会保存在程序根目录下的 **images目录** \n 如果你绘图没有思路,可以在这两个网站寻找灵感。\n - [https://lexica.art/](https://lexica.art/)\n- [https://www.clickprompt.org/zh-CN/](https://www.clickprompt.org/zh-CN/)", rmsg.SenderStaffId) + _, err := rmsg.ReplyToDingtalk(string(public.MARKDOWN), "发送以 **#图片** 开头的内容,将会触发绘画能力,图片生成之后,将会保存在程序根目录下的 **images目录** \n 如果你绘图没有思路,可以在这两个网站寻找灵感。\n - [https://lexica.art/](https://lexica.art/)\n- [https://www.clickprompt.org/zh-CN/](https://www.clickprompt.org/zh-CN/)") if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) } @@ -61,7 +61,7 @@ func ProcessRequest(rmsg *public.ReceiveMsg) error { cacheMsg = fmt.Sprintf("💵 已用: 💲%v\n💵 剩余: 💲%v\n⏳ 有效时间: 从 %v 到 %v\n", fmt.Sprintf("%.2f", rst.TotalUsed), fmt.Sprintf("%.2f", rst.TotalAvailable), t1.Format("2006-01-02 15:04:05"), t2.Format("2006-01-02 15:04:05")) } - _, err := rmsg.ReplyToDingtalk(string(public.TEXT), cacheMsg, rmsg.SenderStaffId) + _, err := rmsg.ReplyToDingtalk(string(public.TEXT), cacheMsg) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) } @@ -87,13 +87,13 @@ func Do(mode string, rmsg *public.ReceiveMsg) error { logger.Info(fmt.Errorf("gpt request error: %v", err)) if strings.Contains(fmt.Sprintf("%v", err), "maximum text length exceeded") { public.UserService.ClearUserSessionContext(rmsg.SenderStaffId) - _, err = rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v,看起来是超过最大对话限制了,已自动重置您的对话", err), rmsg.SenderStaffId) + _, err = rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v,看起来是超过最大对话限制了,已自动重置您的对话", err)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return err } } else { - _, err = rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v", err), rmsg.SenderStaffId) + _, err = rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v", err)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return err @@ -107,8 +107,7 @@ func Do(mode string, rmsg *public.ReceiveMsg) error { reply = strings.TrimSpace(reply) reply = strings.Trim(reply, "\n") // 回复@我的用户 - // fmt.Println("单聊结果是:", reply) - _, err = rmsg.ReplyToDingtalk(string(public.TEXT), reply, rmsg.SenderStaffId) + _, err = rmsg.ReplyToDingtalk(string(public.TEXT), reply) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return err @@ -120,13 +119,13 @@ func Do(mode string, rmsg *public.ReceiveMsg) error { logger.Info(fmt.Sprintf("gpt request error: %v", err)) if strings.Contains(fmt.Sprintf("%v", err), "maximum text length exceeded") { public.UserService.ClearUserSessionContext(rmsg.SenderStaffId) - _, err = rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v,看起来是超过最大对话限制了,已自动重置您的对话", err), rmsg.SenderStaffId) + _, err = rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v,看起来是超过最大对话限制了,已自动重置您的对话", err)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return err } } else { - _, err = rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v", err), rmsg.SenderStaffId) + _, err = rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v", err)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return err @@ -140,7 +139,7 @@ func Do(mode string, rmsg *public.ReceiveMsg) error { reply = strings.TrimSpace(reply) reply = strings.Trim(reply, "\n") // 回复@我的用户 - _, err = rmsg.ReplyToDingtalk(string(public.TEXT), reply, rmsg.SenderStaffId) + _, err = rmsg.ReplyToDingtalk(string(public.TEXT), reply) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return err @@ -157,7 +156,7 @@ func ImageGenerate(rmsg *public.ReceiveMsg) error { reply, err := chatgpt.ImageQa(rmsg.Text.Content, rmsg.SenderStaffId) if err != nil { logger.Info(fmt.Errorf("gpt request error: %v", err)) - _, err = rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v", err), rmsg.SenderStaffId) + _, err = rmsg.ReplyToDingtalk(string(public.TEXT), fmt.Sprintf("请求openai失败了,错误信息:%v", err)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return err @@ -170,7 +169,7 @@ func ImageGenerate(rmsg *public.ReceiveMsg) error { reply = strings.TrimSpace(reply) reply = strings.Trim(reply, "\n") // 回复@我的用户 - _, err = rmsg.ReplyToDingtalk(string(public.MARKDOWN), fmt.Sprintf(">点击图片可旋转或放大。\n![](%s)", reply), rmsg.SenderStaffId) + _, err = rmsg.ReplyToDingtalk(string(public.MARKDOWN), fmt.Sprintf(">点击图片可旋转或放大。\n![](%s)", reply)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return err diff --git a/public/dingtalk.go b/public/dingtalk.go index f7ada15..87fdf59 100644 --- a/public/dingtalk.go +++ b/public/dingtalk.go @@ -3,6 +3,7 @@ package public import ( "bytes" "encoding/json" + "fmt" "net/http" ) @@ -68,15 +69,19 @@ type At struct { } // 发消息给钉钉 -func (r ReceiveMsg) ReplyToDingtalk(msgType, msg, staffId string) (statuscode int, err error) { +func (r ReceiveMsg) ReplyToDingtalk(msgType, msg string) (statuscode int, err error) { + atUser := r.SenderStaffId + if atUser == "" { + msg = fmt.Sprintf("%s\n\n@%s", msg, r.SenderNick) + } var msgtmp interface{} switch msgType { case string(TEXT): - msgtmp = &TextMessage{Text: &Text{Content: msg}, MsgType: TEXT, At: &At{AtUserIds: []string{staffId}}} + msgtmp = &TextMessage{Text: &Text{Content: msg}, MsgType: TEXT, At: &At{AtUserIds: []string{atUser}}} case string(MARKDOWN): - msgtmp = &MarkDownMessage{MsgType: MARKDOWN, At: &At{AtUserIds: []string{staffId}}, MarkDown: &MarkDown{Title: "根据您提供的信息,为您生成图片如下", Text: msg}} + msgtmp = &MarkDownMessage{MsgType: MARKDOWN, At: &At{AtUserIds: []string{atUser}}, MarkDown: &MarkDown{Title: "Markdown Type", Text: msg}} default: - msgtmp = &TextMessage{Text: &Text{Content: msg}, MsgType: TEXT, At: &At{AtUserIds: []string{staffId}}} + msgtmp = &TextMessage{Text: &Text{Content: msg}, MsgType: TEXT, At: &At{AtUserIds: []string{atUser}}} } data, err := json.Marshal(msgtmp) diff --git a/public/public.go b/public/public.go index 0c054e3..0baf72b 100644 --- a/public/public.go +++ b/public/public.go @@ -45,7 +45,7 @@ func CheckRequest(rmsg *ReceiveMsg) bool { // 判断访问次数是否超过限制 if count >= Config.MaxRequest { logger.Info(fmt.Sprintf("亲爱的: %s,您今日请求次数已达上限,请明天再来,交互发问资源有限,请务必斟酌您的问题,给您带来不便,敬请谅解!", rmsg.SenderNick)) - _, err := rmsg.ReplyToDingtalk(string(TEXT), fmt.Sprintf("一个好的问题,胜过十个好的答案!\n亲爱的: %s,您今日请求次数已达上限,请明天再来,交互发问资源有限,请务必斟酌您的问题,给您带来不便,敬请谅解!", rmsg.SenderNick), rmsg.SenderStaffId) + _, err := rmsg.ReplyToDingtalk(string(TEXT), fmt.Sprintf("一个好的问题,胜过十个好的答案!\n亲爱的: %s,您今日请求次数已达上限,请明天再来,交互发问资源有限,请务必斟酌您的问题,给您带来不便,敬请谅解!", rmsg.SenderNick)) if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) }