@@ -15,13 +15,9 @@ type CustomMigrationStatic struct { | |||||
Migrate func(*xorm.Engine, *xorm.Engine) error | Migrate func(*xorm.Engine, *xorm.Engine) error | ||||
} | } | ||||
var customMigrations = []CustomMigration{ | |||||
{"Custom v1 Topic struct change to support chinese", syncTopicStruct}, | |||||
} | |||||
var customMigrations []CustomMigration | |||||
var customMigrationsStatic = []CustomMigrationStatic{ | |||||
{"update issue_fixed_rate to 1 if num_issues is 0 ", updateIssueFixedRate}, | |||||
} | |||||
var customMigrationsStatic []CustomMigrationStatic | |||||
func MigrateCustom(x *xorm.Engine) { | func MigrateCustom(x *xorm.Engine) { | ||||
@@ -181,6 +181,7 @@ func SearchDatasetCondition(opts *SearchDatasetOptions) builder.Cond { | |||||
if len(opts.DatasetIDs) > 0 { | if len(opts.DatasetIDs) > 0 { | ||||
subCon := builder.NewCond() | subCon := builder.NewCond() | ||||
subCon = subCon.And(builder.In("dataset.id", opts.DatasetIDs)) | subCon = subCon.And(builder.In("dataset.id", opts.DatasetIDs)) | ||||
subCon = generateFilterCond(opts, subCon) | |||||
cond = cond.Or(subCon) | cond = cond.Or(subCon) | ||||
} | } | ||||
@@ -460,5 +461,12 @@ func GetCollaboratorDatasetIdsByUserID(userID int64) []int64 { | |||||
_ = x.Table("dataset").Join("INNER", "collaboration", "dataset.repo_id = collaboration.repo_id and collaboration.mode>0 and collaboration.user_id=?", userID). | _ = x.Table("dataset").Join("INNER", "collaboration", "dataset.repo_id = collaboration.repo_id and collaboration.mode>0 and collaboration.user_id=?", userID). | ||||
Cols("dataset.id").Find(&datasets) | Cols("dataset.id").Find(&datasets) | ||||
return datasets | return datasets | ||||
} | |||||
func GetTeamDatasetIdsByUserID(userID int64) []int64 { | |||||
var datasets []int64 | |||||
_ = x.Table("dataset").Join("INNER", "team_repo", "dataset.repo_id = team_repo.repo_id"). | |||||
Join("INNER", "team_user", "team_repo.team_id=team_user.team_id and team_user.uid=?", userID). | |||||
Cols("dataset.id").Find(&datasets) | |||||
return datasets | |||||
} | } |
@@ -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,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 | |||||
} |
@@ -17,7 +17,8 @@ var ( | |||||
const ( | const ( | ||||
GRANT_TYPE = "client_credential" | GRANT_TYPE = "client_credential" | ||||
ACCESS_TOKEN_PATH = "/cgi-bin/token" | 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" | ACTION_QR_STR_SCENE = "QR_STR_SCENE" | ||||
ERR_CODE_ACCESSTOKEN_EXPIRE = 42001 | ERR_CODE_ACCESSTOKEN_EXPIRE = 42001 | ||||
@@ -40,6 +41,11 @@ type QRCodeRequest struct { | |||||
Action_info ActionInfo `json:"action_info"` | Action_info ActionInfo `json:"action_info"` | ||||
Expire_seconds int `json:"expire_seconds"` | Expire_seconds int `json:"expire_seconds"` | ||||
} | } | ||||
type MaterialRequest struct { | |||||
Type string `json:"type"` | |||||
Offset int `json:"offset"` | |||||
Count int `json:"count"` | |||||
} | |||||
type ActionInfo struct { | type ActionInfo struct { | ||||
Scene Scene `json:"scene"` | Scene Scene `json:"scene"` | ||||
@@ -97,7 +103,7 @@ func callQRCodeCreate(sceneStr string) (*QRCodeResponse, bool) { | |||||
SetQueryParam("access_token", GetWechatAccessToken()). | SetQueryParam("access_token", GetWechatAccessToken()). | ||||
SetBody(bodyJson). | SetBody(bodyJson). | ||||
SetResult(&result). | SetResult(&result). | ||||
Post(setting.WechatApiHost + QR_CODE_Path) | |||||
Post(setting.WechatApiHost + QR_CODE_PATH) | |||||
if err != nil { | if err != nil { | ||||
log.Error("create QR code failed,e=%v", err) | log.Error("create QR code failed,e=%v", err) | ||||
return nil, false | return nil, false | ||||
@@ -113,6 +119,37 @@ func callQRCodeCreate(sceneStr string) (*QRCodeResponse, bool) { | |||||
return &result, false | 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 { | func getErrorCodeFromResponse(r *resty.Response) int { | ||||
a := r.Body() | a := r.Body() | ||||
resultMap := make(map[string]interface{}, 0) | resultMap := make(map[string]interface{}, 0) | ||||
@@ -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,13 @@ type WechatEvent struct { | |||||
Event string | Event string | ||||
EventKey string | EventKey string | ||||
Ticket string | Ticket string | ||||
Content string | |||||
MsgId string | |||||
MsgDataId string | |||||
Idx string | |||||
} | } | ||||
type EventReply struct { | |||||
type MsgReply struct { | |||||
XMLName xml.Name `xml:"xml"` | XMLName xml.Name `xml:"xml"` | ||||
ToUserName string | ToUserName string | ||||
FromUserName string | FromUserName string | ||||
@@ -37,16 +41,97 @@ type EventReply struct { | |||||
Content string | 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 ( | const ( | ||||
WECHAT_EVENT_SUBSCRIBE = "subscribe" | WECHAT_EVENT_SUBSCRIBE = "subscribe" | ||||
WECHAT_EVENT_SCAN = "SCAN" | WECHAT_EVENT_SCAN = "SCAN" | ||||
) | ) | ||||
const ( | 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 | eventKey := we.EventKey | ||||
if eventKey == "" { | if eventKey == "" { | ||||
return "" | return "" | ||||
@@ -74,3 +159,11 @@ func HandleSubscribeEvent(we WechatEvent) string { | |||||
return BIND_REPLY_SUCCESS | return BIND_REPLY_SUCCESS | ||||
} | } | ||||
func HandleSubscribeEvent(we WechatMsg) *WechatReplyContent { | |||||
r, err := LoadReplyFromCacheAndDisk(SubscribeReply) | |||||
if err != nil || len(r) == 0 { | |||||
return nil | |||||
} | |||||
return r[0] | |||||
} |
@@ -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 | |||||
} |
@@ -546,6 +546,13 @@ var ( | |||||
WechatQRCodeExpireSeconds int | WechatQRCodeExpireSeconds int | ||||
WechatAuthSwitch bool | WechatAuthSwitch bool | ||||
//wechat auto reply config | |||||
UserNameOfWechatReply string | |||||
RepoNameOfWechatReply string | |||||
RefNameOfWechatReply string | |||||
TreePathOfAutoMsgReply string | |||||
TreePathOfSubscribe string | |||||
//nginx proxy | //nginx proxy | ||||
PROXYURL string | PROXYURL string | ||||
RadarMap = struct { | RadarMap = struct { | ||||
@@ -1374,6 +1381,11 @@ func NewContext() { | |||||
WechatAppSecret = sec.Key("APP_SECRET").MustString("e48e13f315adc32749ddc7057585f198") | WechatAppSecret = sec.Key("APP_SECRET").MustString("e48e13f315adc32749ddc7057585f198") | ||||
WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120) | WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120) | ||||
WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(true) | 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() | SetRadarMapConfig() | ||||
@@ -1423,7 +1423,7 @@ issues.label_templates.helper=选择标签模板 | |||||
issues.label_templates.use=使用标签集 | issues.label_templates.use=使用标签集 | ||||
issues.label_templates.fail_to_load_file=加载标签模板文件 '%s' 时发生错误:%v | issues.label_templates.fail_to_load_file=加载标签模板文件 '%s' 时发生错误:%v | ||||
issues.add_label_at=添加了标签 <div class="ui label" style="color: %s\; background-color: %s"> %s </div> %s | issues.add_label_at=添加了标签 <div class="ui label" style="color: %s\; background-color: %s"> %s </div> %s | ||||
issues.remove_label_at=删除了 <div class="ui label" style="color: %s\; background-color: %s">%s</div> label %s 标签 | |||||
issues.remove_label_at=删除了标签 <div class="ui label" style="color: %s\; background-color: %s">%s </div> %s | |||||
issues.add_milestone_at=` %[2]s 添加了里程碑 <b>%[1]s</b>` | issues.add_milestone_at=` %[2]s 添加了里程碑 <b>%[1]s</b>` | ||||
issues.change_milestone_at=`%[3]s 修改了里程碑从 <b>%[1]s</b> 到 <b>%[2]s</b>` | issues.change_milestone_at=`%[3]s 修改了里程碑从 <b>%[1]s</b> 到 <b>%[2]s</b>` | ||||
issues.remove_milestone_at=`%[2]s 删除了里程碑 <b>%[1]s</b>` | issues.remove_milestone_at=`%[2]s 删除了里程碑 <b>%[1]s</b>` | ||||
@@ -74,28 +74,30 @@ var swiperOrg = new Swiper(".homeorg-list", { | |||||
}, | }, | ||||
}); | }); | ||||
var output = document.getElementById("newmessage"); | |||||
var url = "ws://" + document.location.host + "/action/notification"; | |||||
if(document.location.host == "git.openi.org.cn" || document.URL.startsWith("https")){ | |||||
url = "wss://" + document.location.host + "/action/notification" | |||||
} | |||||
var socket = new WebSocket(url); | |||||
socket.onopen = function () { | |||||
messageQueue = []; | |||||
console.log("message has connected."); | |||||
}; | |||||
var maxSize = 20; | var maxSize = 20; | ||||
var html =document.documentElement; | var html =document.documentElement; | ||||
var lang = html.attributes["lang"] | var lang = html.attributes["lang"] | ||||
var isZh = true; | var isZh = true; | ||||
if(lang != null && lang.nodeValue =="en-US" ){ | if(lang != null && lang.nodeValue =="en-US" ){ | ||||
isZh=false; | isZh=false; | ||||
}else{ | |||||
} | } | ||||
socket.onmessage = function (e) { | |||||
document.onreadystatechange = function () { | |||||
queryRecommendData(); | |||||
var output = document.getElementById("newmessage"); | |||||
var url = "ws://" + document.location.host + "/action/notification"; | |||||
if(document.location.host == "git.openi.org.cn" || document.URL.startsWith("https")){ | |||||
url = "wss://" + document.location.host + "/action/notification" | |||||
} | |||||
var socket = new WebSocket(url); | |||||
socket.onopen = function () { | |||||
messageQueue = []; | |||||
console.log("message has connected."); | |||||
}; | |||||
socket.onmessage = function (e) { | |||||
var data =JSON.parse(e.data) | var data =JSON.parse(e.data) | ||||
var html = ""; | var html = ""; | ||||
if (data != null){ | if (data != null){ | ||||
@@ -176,7 +178,10 @@ socket.onmessage = function (e) { | |||||
output.innerHTML = html; | output.innerHTML = html; | ||||
swiperNewMessage.updateSlides(); | swiperNewMessage.updateSlides(); | ||||
swiperNewMessage.updateProgress(); | swiperNewMessage.updateProgress(); | ||||
}; | |||||
}; | |||||
} | |||||
function getTaskLink(record){ | function getTaskLink(record){ | ||||
var re = getRepoLink(record); | var re = getRepoLink(record); | ||||
@@ -437,7 +442,9 @@ function getAction(opType,isZh){ | |||||
} | } | ||||
} | } | ||||
queryRecommendData(); | |||||
function queryRecommendData(){ | function queryRecommendData(){ | ||||
$.ajax({ | $.ajax({ | ||||
@@ -1052,6 +1052,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
m.Get("/prd/event", authentication.ValidEventSource) | m.Get("/prd/event", authentication.ValidEventSource) | ||||
m.Post("/prd/event", authentication.AcceptWechatEvent) | m.Post("/prd/event", authentication.AcceptWechatEvent) | ||||
}) | }) | ||||
m.Get("/wechat/material", authentication.GetMaterial) | |||||
}, securityHeaders(), context.APIContexter(), sudo()) | }, securityHeaders(), context.APIContexter(), sudo()) | ||||
} | } | ||||
@@ -8,9 +8,11 @@ import ( | |||||
"code.gitea.io/gitea/modules/redis/redis_client" | "code.gitea.io/gitea/modules/redis/redis_client" | ||||
"code.gitea.io/gitea/modules/redis/redis_key" | "code.gitea.io/gitea/modules/redis/redis_key" | ||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"code.gitea.io/gitea/routers/response" | |||||
"encoding/json" | "encoding/json" | ||||
"errors" | "errors" | ||||
gouuid "github.com/satori/go.uuid" | gouuid "github.com/satori/go.uuid" | ||||
"strconv" | |||||
"time" | "time" | ||||
) | ) | ||||
@@ -124,3 +126,23 @@ func createQRCode4Bind(userId int64) (*QRCodeResponse, error) { | |||||
} | } | ||||
return result, nil | 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)) | |||||
} |
@@ -14,24 +14,48 @@ 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) | ||||
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 == "" { | if replyStr == "" { | ||||
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 +63,99 @@ 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 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 | |||||
} | } |
@@ -345,7 +345,9 @@ func ExploreDatasets(ctx *context.Context) { | |||||
var datasetsIds []int64 | var datasetsIds []int64 | ||||
if ownerID > 0 { | if ownerID > 0 { | ||||
datasetsIds = models.GetCollaboratorDatasetIdsByUserID(ownerID) | |||||
collaboratorDatasetsIds := models.GetCollaboratorDatasetIdsByUserID(ownerID) | |||||
teamDatasetsIds := models.GetTeamDatasetIdsByUserID(ownerID) | |||||
datasetsIds = append(collaboratorDatasetsIds, teamDatasetsIds...) | |||||
} | } | ||||
opts := &models.SearchDatasetOptions{ | opts := &models.SearchDatasetOptions{ | ||||
@@ -172,8 +172,8 @@ func DatasetIndex(ctx *context.Context) { | |||||
for _, attachment := range pageAttachments { | for _, attachment := range pageAttachments { | ||||
uploader, _ := models.GetUserByID(attachment.UploaderID) | uploader, _ := models.GetUserByID(attachment.UploaderID) | ||||
attachment.Uploader = uploader | attachment.Uploader = uploader | ||||
if !strings.HasSuffix(attachment.Name, ".zip") { | |||||
attachment.DecompressState = -1 //非zip文件 | |||||
if !strings.HasSuffix(attachment.Name, ".zip") && !strings.HasSuffix(attachment.Name, ".tar.gz") { | |||||
attachment.DecompressState = -1 //非压缩文件 | |||||
} | } | ||||
} | } | ||||
@@ -616,8 +616,14 @@ func DatasetIsCollaborator(ctx *context.Context, dataset *models.Dataset) bool { | |||||
repo.GetOwner() | repo.GetOwner() | ||||
if ctx.User != nil { | if ctx.User != nil { | ||||
if repo.Owner.IsOrganization() { | if repo.Owner.IsOrganization() { | ||||
if repo.Owner.IsUserPartOfOrg(ctx.User.ID) { | |||||
for _, t := range repo.Owner.Teams { | |||||
org := repo.Owner | |||||
org.Teams, err = org.GetUserTeams(ctx.User.ID) | |||||
if err != nil { | |||||
log.Error("GetUserTeams error:", err.Error()) | |||||
return false | |||||
} | |||||
if org.IsUserPartOfOrg(ctx.User.ID) { | |||||
for _, t := range org.Teams { | |||||
if t.IsMember(ctx.User.ID) && t.HasRepository(repo.ID) { | if t.IsMember(ctx.User.ID) && t.HasRepository(repo.ID) { | ||||
return true | return true | ||||
} | } | ||||