|
@@ -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,63 @@ 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 {}
|
|
|
+ }
|
|
|
+}
|
|
|
+func StartStream(clientId, clientSecret string) {
|
|
|
+ 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(OnChatReceive).OnEventReceived),
|
|
|
+ )
|
|
|
+ err := cli.Start(context.Background())
|
|
|
+ if err != nil {
|
|
|
+ panic(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ defer cli.Close()
|
|
|
+
|
|
|
+ select {}
|
|
|
+}
|
|
|
+
|
|
|
+func 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 := public.Config.Credentials[0].ClientID
|
|
|
+ var c gin.Context
|
|
|
+ c.Set(public.DingTalkClientIdKeyName, clientId)
|
|
|
+ DoRequest(msgObj, &c)
|
|
|
+
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
-func Start() {
|
|
|
+func StartHttp() {
|
|
|
app := gin.Default()
|
|
|
app.POST("/", func(c *gin.Context) {
|
|
|
var msgObj dingbot.ReceiveMsg
|
|
@@ -32,142 +90,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 +150,145 @@ 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)
|
|
|
+ }
|
|
|
+ // 为了兼容存量老用户,暂时保留 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:
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|