package channel import ( "bytes" "encoding/json" "errors" "fmt" "message-pusher/common" "message-pusher/model" "net/http" "strings" ) type larkAppTokenRequest struct { AppID string `json:"app_id"` AppSecret string `json:"app_secret"` } type larkAppTokenResponse struct { Code int `json:"code"` Msg string `json:"msg"` TenantAccessToken string `json:"tenant_access_token"` Expire int `json:"expire"` } type LarkAppTokenStoreItem struct { AppID string AppSecret string AccessToken string } func (i *LarkAppTokenStoreItem) Key() string { return i.AppID + i.AppSecret } func (i *LarkAppTokenStoreItem) IsShared() bool { var count int64 = 0 model.DB.Model(&model.Channel{}).Where("secret = ? and app_id = ? and type = ?", i.AppSecret, i.AppID, model.TypeLarkApp).Count(&count) return count > 1 } func (i *LarkAppTokenStoreItem) IsFilled() bool { return i.AppID != "" && i.AppSecret != "" } func (i *LarkAppTokenStoreItem) Token() string { return i.AccessToken } func (i *LarkAppTokenStoreItem) Refresh() { // https://open.feishu.cn/document/ukTMukTMukTM/ukDNz4SO0MjL5QzM/auth-v3/auth/tenant_access_token_internal tokenRequest := larkAppTokenRequest{ AppID: i.AppID, AppSecret: i.AppSecret, } tokenRequestData, err := json.Marshal(tokenRequest) responseData, err := http.Post("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", "application/json; charset=utf-8", bytes.NewBuffer(tokenRequestData)) if err != nil { common.SysError("failed to refresh access token: " + err.Error()) return } defer responseData.Body.Close() var res larkAppTokenResponse err = json.NewDecoder(responseData.Body).Decode(&res) if err != nil { common.SysError("failed to decode larkAppTokenResponse: " + err.Error()) return } if res.Code != 0 { common.SysError(res.Msg) return } i.AccessToken = res.TenantAccessToken common.SysLog("access token refreshed") } type larkAppMessageRequest struct { ReceiveId string `json:"receive_id"` MsgType string `json:"msg_type"` Content string `json:"content"` } type larkAppMessageResponse struct { Code int `json:"code"` Msg string `json:"msg"` } func parseLarkAppTarget(target string) (string, string, error) { parts := strings.Split(target, ":") if len(parts) != 2 { return "", "", errors.New("无效的飞书应用号消息接收者参数") } return parts[0], parts[1], nil } func SendLarkAppMessage(message *model.Message, user *model.User, channel_ *model.Channel) error { // https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/create rawTarget := message.To if rawTarget == "" { rawTarget = channel_.AccountId } targetType, target, err := parseLarkAppTarget(rawTarget) if err != nil { return err } request := larkAppMessageRequest{ ReceiveId: target, } atPrefix := getLarkAtPrefix(message) if message.Description != "" { request.MsgType = "text" content := larkTextContent{Text: atPrefix + message.Description} contentData, err := json.Marshal(content) if err != nil { return err } request.Content = string(contentData) } else { request.MsgType = "interactive" content := larkCardContent{} content.Config.WideScreenMode = true content.Config.EnableForward = true content.Elements = append(content.Elements, larkMessageRequestCardElement{ Tag: "div", Text: larkMessageRequestCardElementText{ Content: atPrefix + message.Content, Tag: "lark_md", }, }) contentData, err := json.Marshal(content) if err != nil { return err } request.Content = string(contentData) } requestData, err := json.Marshal(request) if err != nil { return err } key := fmt.Sprintf("%s%s", channel_.AppId, channel_.Secret) accessToken := TokenStoreGetToken(key) url := fmt.Sprintf("https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=%s", targetType) req, _ := http.NewRequest("POST", url, bytes.NewReader(requestData)) req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("Content-Type", "application/json; charset=utf-8") resp, err := http.DefaultClient.Do(req) if err != nil { return err } var res larkAppMessageResponse err = json.NewDecoder(resp.Body).Decode(&res) if err != nil { return err } if res.Code != 0 { return errors.New(res.Msg) } return nil }