From 44ff7687f213d12521834b056750ff4d4f16c470 Mon Sep 17 00:00:00 2001 From: chenyifan01 Date: Mon, 13 Jun 2022 17:35:39 +0800 Subject: [PATCH] #2225 add auto reply --- models/repo.go | 9 +- modules/auth/wechat/auto_reply.go | 110 +++++++++++++++++++++++++ modules/auth/wechat/event_handle.go | 87 ++++++++++++++++++- routers/authentication/wechat_event.go | 110 ++++++++++++++++++++++--- 4 files changed, 293 insertions(+), 23 deletions(-) create mode 100644 modules/auth/wechat/auto_reply.go diff --git a/models/repo.go b/models/repo.go index db2694617..4770e5415 100755 --- a/models/repo.go +++ b/models/repo.go @@ -2749,15 +2749,10 @@ func ReadLatestFileInRepo(userName, repoName, refName, treePath string) (*RepoFi log.Error("ReadLatestFileInRepo: Close: %v", err) } }() - - buf := make([]byte, 1024) - n, _ := reader.Read(buf) - if n >= 0 { - buf = buf[:n] - } + d, _ := ioutil.ReadAll(reader) commitId := "" if blob != nil { commitId = fmt.Sprint(blob.ID) } - return &RepoFile{CommitId: commitId, Content: buf}, nil + return &RepoFile{CommitId: commitId, Content: d}, nil } diff --git a/modules/auth/wechat/auto_reply.go b/modules/auth/wechat/auto_reply.go new file mode 100644 index 000000000..7d3a30d07 --- /dev/null +++ b/modules/auth/wechat/auto_reply.go @@ -0,0 +1,110 @@ +package wechat + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" + "encoding/json" + "github.com/patrickmn/go-cache" + "strings" + "time" +) + +var WechatReplyCache = cache.New(2*time.Minute, 1*time.Minute) + +const ( + WECHAT_REPLY_CACHE_KEY = "wechat_response" +) + +const ( + ReplyTypeText = "text" + ReplyTypeImage = "image" + ReplyTypeVoice = "voice" + ReplyTypeVideo = "video" + ReplyTypeMusic = "music" + ReplyTypeNews = "news" +) + +type AutomaticResponseContent struct { + Reply *ReplyContent + ReplyType string + KeyWords []string + IsFullMatch int +} + +type ReplyContent struct { + Content string + MediaId string + Title string + Description string + MusicUrl string + HQMusicUrl string + ThumbMediaId string +} + +func GetAutomaticReply(msg string) *AutomaticResponseContent { + r, err := LoadAutomaticReplyFromCacheAndDisk() + if err != nil { + return nil + } + if r == nil || len(r) == 0 { + return nil + } + for i := 0; i < len(r); i++ { + if r[i].IsFullMatch > 0 { + for _, v := range r[i].KeyWords { + if strings.Contains(msg, v) { + return r[i] + } + } + } else if r[i].IsFullMatch == 0 { + for _, v := range r[i].KeyWords { + if msg == v { + return r[i] + } + } + } + } + return nil + +} + +func loadAutomaticReplyFromDisk() ([]*AutomaticResponseContent, error) { + log.Debug("LoadAutomaticResponseMap from disk") + repo, err := models.GetRepositoryByOwnerAndAlias("OpenIOSSG", "promote") + if err != nil { + log.Error("get notice repo failed, error=%v", err) + return nil, err + } + repoFile, err := models.ReadLatestFileInRepo("OpenIOSSG", repo.Name, "master", "wechat/auto_reply.json") + if err != nil { + log.Error("GetNewestNotice failed, error=%v", err) + return nil, err + } + res := make([]*AutomaticResponseContent, 0) + json.Unmarshal(repoFile.Content, &res) + if res == nil || len(res) == 0 { + return nil, err + } + return res, nil +} + +func LoadAutomaticReplyFromCacheAndDisk() ([]*AutomaticResponseContent, error) { + v, success := WechatReplyCache.Get(WECHAT_REPLY_CACHE_KEY) + if success { + log.Debug("LoadAutomaticResponse from cache,value = %v", v) + if v == nil { + return nil, nil + } + n := v.([]*AutomaticResponseContent) + return n, nil + } + + content, err := loadAutomaticReplyFromDisk() + if err != nil { + log.Error("GetNewestNotice failed, error=%v", err) + WechatReplyCache.Set(WECHAT_REPLY_CACHE_KEY, nil, 30*time.Second) + return nil, err + } + WechatReplyCache.Set(WECHAT_REPLY_CACHE_KEY, content, 60*time.Second) + return content, nil +} diff --git a/modules/auth/wechat/event_handle.go b/modules/auth/wechat/event_handle.go index b40ab3101..3dd8508cb 100644 --- a/modules/auth/wechat/event_handle.go +++ b/modules/auth/wechat/event_handle.go @@ -18,7 +18,7 @@ import ( // // // -type WechatEvent struct { +type WechatMsg struct { ToUserName string FromUserName string CreateTime int64 @@ -26,9 +26,22 @@ type WechatEvent struct { Event string EventKey string Ticket string + Content string + MsgId string + MsgDataId string + Idx string +} + +type MsgReply struct { + XMLName xml.Name `xml:"xml"` + ToUserName string + FromUserName string + CreateTime int64 + MsgType string + Content string } -type EventReply struct { +type TextMsgReply struct { XMLName xml.Name `xml:"xml"` ToUserName string FromUserName string @@ -36,6 +49,71 @@ type EventReply struct { MsgType string Content string } +type ImageMsgReply struct { + XMLName xml.Name `xml:"xml"` + ToUserName string + FromUserName string + CreateTime int64 + MsgType string + Image ImageContent +} +type VoiceMsgReply struct { + XMLName xml.Name `xml:"xml"` + ToUserName string + FromUserName string + CreateTime int64 + MsgType string + Voice VoiceContent +} +type VideoMsgReply struct { + XMLName xml.Name `xml:"xml"` + ToUserName string + FromUserName string + CreateTime int64 + MsgType string + Video VideoContent +} +type MusicMsgReply struct { + XMLName xml.Name `xml:"xml"` + ToUserName string + FromUserName string + CreateTime int64 + MsgType string + Music MusicContent +} +type ArticlesMsgReply struct { + XMLName xml.Name `xml:"xml"` + ToUserName string + FromUserName string + CreateTime int64 + MsgType string + Articles []ArticlesContent +} + +type ImageContent struct { + MediaId string +} +type VoiceContent struct { + MediaId string +} +type VideoContent struct { + MediaId string + Title string + Description string +} +type MusicContent struct { + Title string + Description string + MusicUrl string + HQMusicUrl string + ThumbMediaId string +} +type ArticlesContent struct { + Title string + Description string + PicUrl string + Url string +} const ( WECHAT_EVENT_SUBSCRIBE = "subscribe" @@ -43,10 +121,11 @@ const ( ) const ( - WECHAT_MSG_TYPE_TEXT = "text" + WECHAT_MSG_TYPE_TEXT = "text" + WECHAT_MSG_TYPE_EVENT = "event" ) -func HandleSubscribeEvent(we WechatEvent) string { +func HandleSubscribeEvent(we WechatMsg) string { eventKey := we.EventKey if eventKey == "" { return "" diff --git a/routers/authentication/wechat_event.go b/routers/authentication/wechat_event.go index 9b1cebec6..b8270faee 100644 --- a/routers/authentication/wechat_event.go +++ b/routers/authentication/wechat_event.go @@ -14,14 +14,30 @@ import ( // https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html func AcceptWechatEvent(ctx *context.Context) { b, _ := ioutil.ReadAll(ctx.Req.Request.Body) - we := wechat.WechatEvent{} + we := wechat.WechatMsg{} xml.Unmarshal(b, &we) - + switch we.MsgType { + case wechat.WECHAT_MSG_TYPE_EVENT: + HandleEventMsg(ctx, we) + case wechat.WECHAT_MSG_TYPE_TEXT: + HandleTextMsg(ctx, we) + } log.Info("accept wechat event= %+v", we) + +} + +// ValidEventSource +func ValidEventSource(ctx *context.Context) { + echostr := ctx.Query("echostr") + ctx.Write([]byte(echostr)) + return +} + +func HandleEventMsg(ctx *context.Context, msg wechat.WechatMsg) { var replyStr string - switch we.Event { + switch msg.Event { case wechat.WECHAT_EVENT_SUBSCRIBE, wechat.WECHAT_EVENT_SCAN: - replyStr = wechat.HandleSubscribeEvent(we) + replyStr = wechat.HandleSubscribeEvent(msg) break } @@ -29,9 +45,9 @@ func AcceptWechatEvent(ctx *context.Context) { log.Info("reply str is empty") return } - reply := &wechat.EventReply{ - ToUserName: we.FromUserName, - FromUserName: we.ToUserName, + reply := &wechat.MsgReply{ + ToUserName: msg.FromUserName, + FromUserName: msg.ToUserName, CreateTime: time.Now().Unix(), MsgType: wechat.WECHAT_MSG_TYPE_TEXT, Content: replyStr, @@ -39,9 +55,79 @@ func AcceptWechatEvent(ctx *context.Context) { ctx.XML(200, reply) } -// ValidEventSource -func ValidEventSource(ctx *context.Context) { - echostr := ctx.Query("echostr") - ctx.Write([]byte(echostr)) - return +func HandleTextMsg(ctx *context.Context, msg wechat.WechatMsg) { + r := wechat.GetAutomaticReply(msg.Content) + if r == nil { + log.Info("TextMsg reply is empty") + return + } + reply := buildReplyContent(msg, r) + ctx.XML(200, reply) +} + +func buildReplyContent(msg wechat.WechatMsg, r *wechat.AutomaticResponseContent) interface{} { + reply := &wechat.MsgReply{ + ToUserName: msg.FromUserName, + FromUserName: msg.ToUserName, + CreateTime: time.Now().Unix(), + MsgType: r.ReplyType, + } + switch r.ReplyType { + case wechat.ReplyTypeText: + return &wechat.TextMsgReply{ + ToUserName: msg.FromUserName, + FromUserName: msg.ToUserName, + CreateTime: time.Now().Unix(), + MsgType: r.ReplyType, + Content: r.Reply.Content, + } + + case wechat.ReplyTypeImage: + return &wechat.ImageMsgReply{ + ToUserName: msg.FromUserName, + FromUserName: msg.ToUserName, + CreateTime: time.Now().Unix(), + MsgType: r.ReplyType, + Image: wechat.ImageContent{ + MediaId: r.Reply.MediaId, + }, + } + case wechat.ReplyTypeVoice: + return &wechat.VoiceMsgReply{ + ToUserName: msg.FromUserName, + FromUserName: msg.ToUserName, + CreateTime: time.Now().Unix(), + MsgType: r.ReplyType, + Voice: wechat.VoiceContent{ + MediaId: r.Reply.MediaId, + }, + } + case wechat.ReplyTypeVideo: + return &wechat.VideoMsgReply{ + ToUserName: msg.FromUserName, + FromUserName: msg.ToUserName, + CreateTime: time.Now().Unix(), + MsgType: r.ReplyType, + Video: wechat.VideoContent{ + MediaId: r.Reply.MediaId, + Title: r.Reply.Title, + Description: r.Reply.Description, + }, + } + case wechat.ReplyTypeMusic: + return &wechat.MusicMsgReply{ + ToUserName: msg.FromUserName, + FromUserName: msg.ToUserName, + CreateTime: time.Now().Unix(), + MsgType: r.ReplyType, + Music: wechat.MusicContent{ + Title: r.Reply.Title, + Description: r.Reply.Description, + MusicUrl: r.Reply.MusicUrl, + HQMusicUrl: r.Reply.HQMusicUrl, + ThumbMediaId: r.Reply.ThumbMediaId, + }, + } + } + return reply }