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..440f6de6a
--- /dev/null
+++ b/modules/auth/wechat/auto_reply.go
@@ -0,0 +1,139 @@
+package wechat
+
+import (
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "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 ReplyConfigType string
+
+const (
+ SubscribeReply ReplyConfigType = "subscribe"
+ AutoMsgReply ReplyConfigType = "autoMsg"
+)
+
+func (r ReplyConfigType) Name() string {
+ switch r {
+ case SubscribeReply:
+ return "subscribe"
+ case AutoMsgReply:
+ return "autoMsg"
+ }
+ return ""
+}
+
+func (r ReplyConfigType) TreePath() string {
+ switch r {
+ case SubscribeReply:
+ return setting.TreePathOfSubscribe
+ case AutoMsgReply:
+ return setting.TreePathOfAutoMsgReply
+ }
+ return ""
+}
+
+type WechatReplyContent 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
+ Articles []ArticlesContent
+}
+
+func GetAutomaticReply(msg string) *WechatReplyContent {
+ r, err := LoadReplyFromCacheAndDisk(AutoMsgReply)
+ 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 loadReplyFromDisk(replyConfig ReplyConfigType) ([]*WechatReplyContent, error) {
+ log.Info("LoadReply from disk")
+ repo, err := models.GetRepositoryByOwnerAndAlias(setting.UserNameOfWechatReply, setting.RepoNameOfWechatReply)
+ if err != nil {
+ log.Error("get AutomaticReply repo failed, error=%v", err)
+ return nil, err
+ }
+ repoFile, err := models.ReadLatestFileInRepo(setting.UserNameOfWechatReply, repo.Name, setting.RefNameOfWechatReply, replyConfig.TreePath())
+ if err != nil {
+ log.Error("get AutomaticReply failed, error=%v", err)
+ return nil, err
+ }
+ res := make([]*WechatReplyContent, 0)
+ json.Unmarshal(repoFile.Content, &res)
+ if res == nil || len(res) == 0 {
+ return nil, err
+ }
+ return res, nil
+}
+
+func LoadReplyFromCacheAndDisk(replyConfig ReplyConfigType) ([]*WechatReplyContent, error) {
+ v, success := WechatReplyCache.Get(replyConfig.Name())
+ if success {
+ log.Info("LoadReply from cache,value = %v", v)
+ if v == nil {
+ return nil, nil
+ }
+ n := v.([]*WechatReplyContent)
+ return n, nil
+ }
+
+ content, err := loadReplyFromDisk(replyConfig)
+ if err != nil {
+ log.Error("LoadReply failed, error=%v", err)
+ WechatReplyCache.Set(replyConfig.Name(), nil, 30*time.Second)
+ return nil, err
+ }
+ WechatReplyCache.Set(replyConfig.Name(), content, 60*time.Second)
+ return content, nil
+}
diff --git a/modules/auth/wechat/client.go b/modules/auth/wechat/client.go
index 6734977a1..9ed4b543f 100644
--- a/modules/auth/wechat/client.go
+++ b/modules/auth/wechat/client.go
@@ -17,7 +17,8 @@ var (
const (
GRANT_TYPE = "client_credential"
ACCESS_TOKEN_PATH = "/cgi-bin/token"
- QR_CODE_Path = "/cgi-bin/qrcode/create"
+ QR_CODE_PATH = "/cgi-bin/qrcode/create"
+ GET_MATERIAL_PATH = "/cgi-bin/material/batchget_material"
ACTION_QR_STR_SCENE = "QR_STR_SCENE"
ERR_CODE_ACCESSTOKEN_EXPIRE = 42001
@@ -40,6 +41,11 @@ type QRCodeRequest struct {
Action_info ActionInfo `json:"action_info"`
Expire_seconds int `json:"expire_seconds"`
}
+type MaterialRequest struct {
+ Type string `json:"type"`
+ Offset int `json:"offset"`
+ Count int `json:"count"`
+}
type ActionInfo struct {
Scene Scene `json:"scene"`
@@ -97,7 +103,7 @@ func callQRCodeCreate(sceneStr string) (*QRCodeResponse, bool) {
SetQueryParam("access_token", GetWechatAccessToken()).
SetBody(bodyJson).
SetResult(&result).
- Post(setting.WechatApiHost + QR_CODE_Path)
+ Post(setting.WechatApiHost + QR_CODE_PATH)
if err != nil {
log.Error("create QR code failed,e=%v", err)
return nil, false
@@ -113,6 +119,37 @@ func callQRCodeCreate(sceneStr string) (*QRCodeResponse, bool) {
return &result, false
}
+//getMaterial
+// api doc: https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Get_materials_list.html
+func getMaterial(mType string, offset, count int) (interface{}, bool) {
+ client := getWechatRestyClient()
+
+ body := &MaterialRequest{
+ Type: mType,
+ Offset: offset,
+ Count: count,
+ }
+ bodyJson, _ := json.Marshal(body)
+ r, err := client.R().
+ SetHeader("Content-Type", "application/json").
+ SetQueryParam("access_token", GetWechatAccessToken()).
+ SetBody(bodyJson).
+ Post(setting.WechatApiHost + GET_MATERIAL_PATH)
+ if err != nil {
+ log.Error("create QR code failed,e=%v", err)
+ return nil, false
+ }
+ a := r.Body()
+ resultMap := make(map[string]interface{}, 0)
+ json.Unmarshal(a, &resultMap)
+ errcode := resultMap["errcode"]
+ if errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_EXPIRE) || errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_INVALID) {
+ return nil, true
+ }
+ log.Info("%v", r)
+ return &resultMap, false
+}
+
func getErrorCodeFromResponse(r *resty.Response) int {
a := r.Body()
resultMap := make(map[string]interface{}, 0)
diff --git a/modules/auth/wechat/event_handle.go b/modules/auth/wechat/event_handle.go
index b40ab3101..27edf7343 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,13 @@ type WechatEvent struct {
Event string
EventKey string
Ticket string
+ Content string
+ MsgId string
+ MsgDataId string
+ Idx string
}
-type EventReply struct {
+type MsgReply struct {
XMLName xml.Name `xml:"xml"`
ToUserName string
FromUserName string
@@ -37,16 +41,97 @@ type EventReply struct {
Content string
}
+type TextMsgReply struct {
+ XMLName xml.Name `xml:"xml"`
+ ToUserName string
+ FromUserName string
+ CreateTime int64
+ 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 NewsMsgReply struct {
+ XMLName xml.Name `xml:"xml"`
+ ToUserName string
+ FromUserName string
+ CreateTime int64
+ MsgType string
+ ArticleCount int
+ Articles ArticleItem
+}
+
+type ArticleItem struct {
+ Item []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 {
+ XMLName xml.Name `xml:"item"`
+ Title string
+ Description string
+ PicUrl string
+ Url string
+}
+
const (
WECHAT_EVENT_SUBSCRIBE = "subscribe"
WECHAT_EVENT_SCAN = "SCAN"
)
const (
- WECHAT_MSG_TYPE_TEXT = "text"
+ WECHAT_MSG_TYPE_TEXT = "text"
+ WECHAT_MSG_TYPE_EVENT = "event"
)
-func HandleSubscribeEvent(we WechatEvent) string {
+func HandleScanEvent(we WechatMsg) string {
eventKey := we.EventKey
if eventKey == "" {
return ""
@@ -74,3 +159,11 @@ func HandleSubscribeEvent(we WechatEvent) string {
return BIND_REPLY_SUCCESS
}
+
+func HandleSubscribeEvent(we WechatMsg) *WechatReplyContent {
+ r, err := LoadReplyFromCacheAndDisk(SubscribeReply)
+ if err != nil || len(r) == 0 {
+ return nil
+ }
+ return r[0]
+}
diff --git a/modules/auth/wechat/material.go b/modules/auth/wechat/material.go
new file mode 100644
index 000000000..526156af5
--- /dev/null
+++ b/modules/auth/wechat/material.go
@@ -0,0 +1,13 @@
+package wechat
+
+import "code.gitea.io/gitea/modules/log"
+
+func GetWechatMaterial(mType string, offset, count int) interface{} {
+ result, retryFlag := getMaterial(mType, offset, count)
+ if retryFlag {
+ log.Info("retryGetWechatMaterial calling")
+ refreshAccessToken()
+ result, _ = getMaterial(mType, offset, count)
+ }
+ return result
+}
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 5c87b68c5..412bc09b1 100755
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -545,6 +545,13 @@ var (
WechatQRCodeExpireSeconds int
WechatAuthSwitch bool
+ //wechat auto reply config
+ UserNameOfWechatReply string
+ RepoNameOfWechatReply string
+ RefNameOfWechatReply string
+ TreePathOfAutoMsgReply string
+ TreePathOfSubscribe string
+
//nginx proxy
PROXYURL string
RadarMap = struct {
@@ -1372,6 +1379,11 @@ func NewContext() {
WechatAppSecret = sec.Key("APP_SECRET").MustString("e48e13f315adc32749ddc7057585f198")
WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120)
WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(true)
+ UserNameOfWechatReply = sec.Key("AUTO_REPLY_USER_NAME").MustString("OpenIOSSG")
+ RepoNameOfWechatReply = sec.Key("AUTO_REPLY_REPO_NAME").MustString("promote")
+ RefNameOfWechatReply = sec.Key("AUTO_REPLY_REF_NAME").MustString("master")
+ TreePathOfAutoMsgReply = sec.Key("AUTO_REPLY_TREE_PATH").MustString("wechat/auto_reply.json")
+ TreePathOfSubscribe = sec.Key("SUBSCRIBE_TREE_PATH").MustString("wechat/subscribe_reply.json")
SetRadarMapConfig()
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 0c280b0cb..963bf2cee 100755
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -1046,6 +1046,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/prd/event", authentication.ValidEventSource)
m.Post("/prd/event", authentication.AcceptWechatEvent)
})
+ m.Get("/wechat/material", authentication.GetMaterial)
}, securityHeaders(), context.APIContexter(), sudo())
}
diff --git a/routers/authentication/wechat.go b/routers/authentication/wechat.go
index 72871afb3..f4a31ea0c 100644
--- a/routers/authentication/wechat.go
+++ b/routers/authentication/wechat.go
@@ -8,9 +8,11 @@ import (
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/routers/response"
"encoding/json"
"errors"
gouuid "github.com/satori/go.uuid"
+ "strconv"
"time"
)
@@ -124,3 +126,23 @@ func createQRCode4Bind(userId int64) (*QRCodeResponse, error) {
}
return result, nil
}
+
+// GetMaterial
+func GetMaterial(ctx *context.Context) {
+ mType := ctx.Query("type")
+ offsetStr := ctx.Query("offset")
+ countStr := ctx.Query("count")
+ var offset, count int
+ if offsetStr == "" {
+ offset = 0
+ } else {
+ offset, _ = strconv.Atoi(offsetStr)
+ }
+ if countStr == "" {
+ count = 20
+ } else {
+ count, _ = strconv.Atoi(countStr)
+ }
+ r := wechat.GetWechatMaterial(mType, offset, count)
+ ctx.JSON(200, response.SuccessWithData(r))
+}
diff --git a/routers/authentication/wechat_event.go b/routers/authentication/wechat_event.go
index 9b1cebec6..887bfba0d 100644
--- a/routers/authentication/wechat_event.go
+++ b/routers/authentication/wechat_event.go
@@ -14,24 +14,48 @@ 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)
- var replyStr string
- switch we.Event {
- case wechat.WECHAT_EVENT_SUBSCRIBE, wechat.WECHAT_EVENT_SCAN:
- replyStr = wechat.HandleSubscribeEvent(we)
- break
+
+}
+
+// ValidEventSource
+func ValidEventSource(ctx *context.Context) {
+ echostr := ctx.Query("echostr")
+ ctx.Write([]byte(echostr))
+ return
+}
+
+func HandleEventMsg(ctx *context.Context, msg wechat.WechatMsg) {
+ switch msg.Event {
+ case wechat.WECHAT_EVENT_SCAN:
+ HandleEventScan(ctx, msg)
+ case wechat.WECHAT_EVENT_SUBSCRIBE:
+ if msg.EventKey != "" {
+ HandleEventScan(ctx, msg)
+ } else {
+ HandleEventSubscribe(ctx, msg)
+ }
+
}
+}
+func HandleEventScan(ctx *context.Context, msg wechat.WechatMsg) {
+ replyStr := wechat.HandleScanEvent(msg)
if replyStr == "" {
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 +63,99 @@ 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 HandleEventSubscribe(ctx *context.Context, msg wechat.WechatMsg) {
+ r := wechat.HandleSubscribeEvent(msg)
+ if r == nil {
+ return
+ }
+ reply := buildReplyContent(msg, r)
+ ctx.XML(200, reply)
+}
+
+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.WechatReplyContent) 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,
+ },
+ }
+ case wechat.ReplyTypeNews:
+ return &wechat.NewsMsgReply{
+ ToUserName: msg.FromUserName,
+ FromUserName: msg.ToUserName,
+ CreateTime: time.Now().Unix(),
+ MsgType: r.ReplyType,
+ ArticleCount: len(r.Reply.Articles),
+ Articles: wechat.ArticleItem{
+ Item: r.Reply.Articles},
+ }
+
+ }
+ return reply
}