@@ -2749,15 +2749,10 @@ func ReadLatestFileInRepo(userName, repoName, refName, treePath string) (*RepoFi | |||||
log.Error("ReadLatestFileInRepo: Close: %v", err) | 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 := "" | commitId := "" | ||||
if blob != nil { | if blob != nil { | ||||
commitId = fmt.Sprint(blob.ID) | commitId = fmt.Sprint(blob.ID) | ||||
} | } | ||||
return &RepoFile{CommitId: commitId, Content: buf}, nil | |||||
return &RepoFile{CommitId: commitId, Content: d}, nil | |||||
} | } |
@@ -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 | |||||
} |
@@ -18,7 +18,7 @@ import ( | |||||
// <EventKey><![CDATA[SCENE_VALUE]]></EventKey> | // <EventKey><![CDATA[SCENE_VALUE]]></EventKey> | ||||
// <Ticket><![CDATA[TICKET]]></Ticket> | // <Ticket><![CDATA[TICKET]]></Ticket> | ||||
//</xml> | //</xml> | ||||
type WechatEvent struct { | |||||
type WechatMsg struct { | |||||
ToUserName string | ToUserName string | ||||
FromUserName string | FromUserName string | ||||
CreateTime int64 | CreateTime int64 | ||||
@@ -26,9 +26,22 @@ type WechatEvent struct { | |||||
Event string | Event string | ||||
EventKey string | EventKey string | ||||
Ticket 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"` | XMLName xml.Name `xml:"xml"` | ||||
ToUserName string | ToUserName string | ||||
FromUserName string | FromUserName string | ||||
@@ -36,6 +49,71 @@ type EventReply struct { | |||||
MsgType string | MsgType string | ||||
Content 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 ( | const ( | ||||
WECHAT_EVENT_SUBSCRIBE = "subscribe" | WECHAT_EVENT_SUBSCRIBE = "subscribe" | ||||
@@ -43,10 +121,11 @@ const ( | |||||
) | ) | ||||
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 | eventKey := we.EventKey | ||||
if eventKey == "" { | if eventKey == "" { | ||||
return "" | return "" | ||||
@@ -14,14 +14,30 @@ import ( | |||||
// https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html | // https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html | ||||
func AcceptWechatEvent(ctx *context.Context) { | func AcceptWechatEvent(ctx *context.Context) { | ||||
b, _ := ioutil.ReadAll(ctx.Req.Request.Body) | b, _ := ioutil.ReadAll(ctx.Req.Request.Body) | ||||
we := wechat.WechatEvent{} | |||||
we := wechat.WechatMsg{} | |||||
xml.Unmarshal(b, &we) | 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) | 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 | var replyStr string | ||||
switch we.Event { | |||||
switch msg.Event { | |||||
case wechat.WECHAT_EVENT_SUBSCRIBE, wechat.WECHAT_EVENT_SCAN: | case wechat.WECHAT_EVENT_SUBSCRIBE, wechat.WECHAT_EVENT_SCAN: | ||||
replyStr = wechat.HandleSubscribeEvent(we) | |||||
replyStr = wechat.HandleSubscribeEvent(msg) | |||||
break | break | ||||
} | } | ||||
@@ -29,9 +45,9 @@ func AcceptWechatEvent(ctx *context.Context) { | |||||
log.Info("reply str is empty") | log.Info("reply str is empty") | ||||
return | return | ||||
} | } | ||||
reply := &wechat.EventReply{ | |||||
ToUserName: we.FromUserName, | |||||
FromUserName: we.ToUserName, | |||||
reply := &wechat.MsgReply{ | |||||
ToUserName: msg.FromUserName, | |||||
FromUserName: msg.ToUserName, | |||||
CreateTime: time.Now().Unix(), | CreateTime: time.Now().Unix(), | ||||
MsgType: wechat.WECHAT_MSG_TYPE_TEXT, | MsgType: wechat.WECHAT_MSG_TYPE_TEXT, | ||||
Content: replyStr, | Content: replyStr, | ||||
@@ -39,9 +55,79 @@ func AcceptWechatEvent(ctx *context.Context) { | |||||
ctx.XML(200, reply) | 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 | |||||
} | } |