Преглед на файлове

feat: 优化钉钉机器人的消息发送方式,兼容outgoing模式 (#128)

Co-authored-by: eryajf <eryajf@users.noreply.github.com>
二丫讲梵 преди 2 години
родител
ревизия
b7afcfd952
променени са 5 файла, в които са добавени 46 реда и са изтрити 21 реда
  1. 20 0
      README.md
  2. 2 1
      main.go
  3. 14 15
      pkg/process/process_request.go
  4. 9 4
      public/dingtalk.go
  5. 1 1
      public/public.go

+ 20 - 0
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. 创建机器人。

+ 2 - 1
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))

+ 14 - 15
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

+ 9 - 4
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)

+ 1 - 1
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))
 		}