feat: support lark app now (close #41)

This commit is contained in:
JustSong
2023-05-08 10:39:30 +08:00
parent 00a9f02974
commit 5b5a56122b
8 changed files with 297 additions and 42 deletions
+161
View File
@@ -0,0 +1,161 @@
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
}
+27 -18
View File
@@ -25,20 +25,24 @@ type larkMessageRequestCardElement struct {
Text larkMessageRequestCardElementText `json:"text"`
}
type larkTextContent struct {
Text string `json:"text"`
}
type larkCardContent struct {
Config struct {
WideScreenMode bool `json:"wide_screen_mode"`
EnableForward bool `json:"enable_forward"`
}
Elements []larkMessageRequestCardElement `json:"elements"`
}
type larkMessageRequest struct {
MessageType string `json:"msg_type"`
Timestamp string `json:"timestamp"`
Sign string `json:"sign"`
Content struct {
Text string `json:"text"`
} `json:"content"`
Card struct {
Config struct {
WideScreenMode bool `json:"wide_screen_mode"`
EnableForward bool `json:"enable_forward"`
}
Elements []larkMessageRequestCardElement `json:"elements"`
} `json:"card"`
MessageType string `json:"msg_type"`
Timestamp string `json:"timestamp"`
Sign string `json:"sign"`
Content larkTextContent `json:"content"`
Card larkCardContent `json:"card"`
}
type larkMessageResponse struct {
@@ -46,11 +50,7 @@ type larkMessageResponse struct {
Message string `json:"msg"`
}
func SendLarkMessage(message *model.Message, user *model.User, channel_ *model.Channel) error {
// https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN#e1cdee9f
messageRequest := larkMessageRequest{
MessageType: "text",
}
func getLarkAtPrefix(message *model.Message) string {
atPrefix := ""
if message.To != "" {
if message.To == "@all" {
@@ -62,6 +62,15 @@ func SendLarkMessage(message *model.Message, user *model.User, channel_ *model.C
}
}
}
return atPrefix
}
func SendLarkMessage(message *model.Message, user *model.User, channel_ *model.Channel) error {
// https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN#e1cdee9f
messageRequest := larkMessageRequest{
MessageType: "text",
}
atPrefix := getLarkAtPrefix(message)
if message.Content == "" {
messageRequest.MessageType = "text"
messageRequest.Content.Text = atPrefix + message.Description
+2
View File
@@ -33,6 +33,8 @@ func SendMessage(message *model.Message, user *model.User, channel_ *model.Chann
return SendOneBotMessage(message, user, channel_)
case model.TypeGroup:
return SendGroupMessage(message, user, channel_)
case model.TypeLarkApp:
return SendLarkAppMessage(message, user, channel_)
default:
return errors.New("不支持的消息通道:" + channel_.Type)
}
+15 -4
View File
@@ -24,13 +24,14 @@ type tokenStore struct {
var s tokenStore
func channel2item(channel_ *model.Channel) TokenStoreItem {
if channel_.Type == model.TypeWeChatTestAccount {
switch channel_.Type {
case model.TypeWeChatTestAccount:
item := &WeChatTestAccountTokenStoreItem{
AppID: channel_.AppId,
AppSecret: channel_.Secret,
}
return item
} else if channel_.Type == model.TypeWeChatCorpAccount {
case model.TypeWeChatCorpAccount:
corpId, agentId, err := parseWechatCorpAccountAppId(channel_.AppId)
if err != nil {
common.SysError(err.Error())
@@ -42,6 +43,12 @@ func channel2item(channel_ *model.Channel) TokenStoreItem {
AgentId: agentId,
}
return item
case model.TypeLarkApp:
item := &LarkAppTokenStoreItem{
AppID: channel_.AppId,
AppSecret: channel_.Secret,
}
return item
}
return nil
}
@@ -146,8 +153,12 @@ func TokenStoreRemoveUser(user *model.User) {
}
}
func checkTokenStoreChannelType(channelType string) bool {
return channelType == model.TypeWeChatTestAccount || channelType == model.TypeWeChatCorpAccount || channelType == model.TypeLarkApp
}
func TokenStoreAddChannel(channel *model.Channel) {
if channel.Type != model.TypeWeChatTestAccount && channel.Type != model.TypeWeChatCorpAccount {
if !checkTokenStoreChannelType(channel.Type) {
return
}
item := channel2item(channel)
@@ -158,7 +169,7 @@ func TokenStoreAddChannel(channel *model.Channel) {
}
func TokenStoreRemoveChannel(channel *model.Channel) {
if channel.Type != model.TypeWeChatTestAccount && channel.Type != model.TypeWeChatCorpAccount {
if !checkTokenStoreChannelType(channel.Type) {
return
}
item := channel2item(channel)