✨ 飞书机器人通知实现
This commit is contained in:
parent
f349497b01
commit
921680d444
|
@ -36,6 +36,7 @@ func NewManager(senders ...Sender) *Manager {
|
|||
return manager
|
||||
}
|
||||
|
||||
// Manager 通知管理器,可用于将通知同时发送至多个渠道
|
||||
type Manager struct {
|
||||
senders []Sender
|
||||
notifyChannel chan Notify
|
||||
|
@ -47,6 +48,7 @@ func (slf *Manager) PushNotify(notify Notify) {
|
|||
slf.notifyChannel <- notify
|
||||
}
|
||||
|
||||
// Release 释放通知管理器
|
||||
func (slf *Manager) Release() {
|
||||
slf.closeChannel <- struct{}{}
|
||||
}
|
||||
|
|
|
@ -2,34 +2,22 @@ package notifies
|
|||
|
||||
import "encoding/json"
|
||||
|
||||
const (
|
||||
FeiShuMsgTypeText = "text" // 文本
|
||||
FeiShuMsgTypeRichText = "post" // 富文本
|
||||
FeiShuMsgTypeImage = "image" // 图片
|
||||
FeiShuMsgTypeInteractive = "interactive" // 消息卡片
|
||||
FeiShuMsgTypeShareChat = "share_chat" // 分享群名片
|
||||
FeiShuMsgTypeShareUser = "share_user" // 分享个人名片
|
||||
FeiShuMsgTypeAudio = "audio" // 音频
|
||||
FeiShuMsgTypeMedia = "media" // 视频
|
||||
FeiShuMsgTypeFile = "file" // 文件
|
||||
FeiShuMsgTypeSticker = "sticker" // 表情包
|
||||
|
||||
)
|
||||
|
||||
func NewFeiShu(msgType, receiveId, content string) *FeiShu {
|
||||
return &FeiShu{
|
||||
ReceiveId: receiveId,
|
||||
Content: content,
|
||||
MsgType: msgType,
|
||||
// NewFeiShu 创建飞书通知消息
|
||||
func NewFeiShu(message FeiShuMessage) *FeiShu {
|
||||
feishu := &FeiShu{
|
||||
Content: map[string]any{},
|
||||
}
|
||||
message(feishu)
|
||||
return feishu
|
||||
}
|
||||
|
||||
// FeiShu 飞书通知消息
|
||||
type FeiShu struct {
|
||||
ReceiveId string `json:"receive_id"`
|
||||
Content string `json:"content"`
|
||||
MsgType string `json:"msg_type"`
|
||||
Content any `json:"content"`
|
||||
MsgType string `json:"msg_type"`
|
||||
}
|
||||
|
||||
// Format 格式化通知内容
|
||||
func (slf *FeiShu) Format() (string, error) {
|
||||
data, err := json.Marshal(slf)
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
package notifies
|
||||
|
||||
const (
|
||||
FeiShuMsgTypeText = "text" // 文本
|
||||
FeiShuMsgTypeRichText = "post" // 富文本
|
||||
FeiShuMsgTypeImage = "image" // 图片
|
||||
FeiShuMsgTypeInteractive = "interactive" // 消息卡片
|
||||
FeiShuMsgTypeShareChat = "share_chat" // 分享群名片
|
||||
FeiShuMsgTypeShareUser = "share_user" // 分享个人名片
|
||||
FeiShuMsgTypeAudio = "audio" // 音频
|
||||
FeiShuMsgTypeMedia = "media" // 视频
|
||||
FeiShuMsgTypeFile = "file" // 文件
|
||||
FeiShuMsgTypeSticker = "sticker" // 表情包
|
||||
)
|
||||
|
||||
const (
|
||||
FeiShuStyleBold = "bold" // 加粗
|
||||
FeiShuStyleUnderline = "underline" // 下划线
|
||||
FeiShuStyleLineThrough = "lineThrough" // 删除线
|
||||
FeiShuStyleItalic = "italic" // 斜体
|
||||
)
|
||||
|
||||
type FeiShuMessage func(feishu *FeiShu)
|
||||
|
||||
// FeiShuMessageWithText 飞书文本消息
|
||||
// - 支持通过换行符进行消息换行
|
||||
// - 支持通过 <at user_id="OpenID">名字</at> 进行@用户
|
||||
// - 支持通过 <at user_id="all">所有人</at> 进行@所有人(必须满足所在群开启@所有人功能。)
|
||||
//
|
||||
// 支持加粗、斜体、下划线、删除线四种样式,可嵌套使用:
|
||||
// - 加粗: <b>文本示例</b>
|
||||
// - 斜体: <i>文本示例</i>
|
||||
// - 下划线 : <u>文本示例</u>
|
||||
// - 删除线: <s>文本示例</s>
|
||||
//
|
||||
// 超链接使用说明
|
||||
// - 超链接的使用格式为[文本](链接), 如[Feishu Open Platform](https://open.feishu.cn) 。
|
||||
// - 请确保链接是合法的,否则会以原始内容发送消息。
|
||||
func FeiShuMessageWithText(text string) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = struct {
|
||||
Text string `json:"text"`
|
||||
}{text}
|
||||
feishu.MsgType = FeiShuMsgTypeText
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuMessageWithRichText 飞书富文本消息
|
||||
func FeiShuMessageWithRichText(richText *FeiShuRichText) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = struct {
|
||||
Post any `json:"post,omitempty"`
|
||||
}{richText.content}
|
||||
feishu.MsgType = FeiShuMsgTypeRichText
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuMessageWithImage 飞书图片消息
|
||||
// - imageKey 可通过上传图片接口获取
|
||||
func FeiShuMessageWithImage(imageKey string) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = struct {
|
||||
ImageKey string `json:"imageKey"`
|
||||
}{imageKey}
|
||||
feishu.MsgType = FeiShuMsgTypeImage
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuMessageWithInteractive 飞书卡片消息
|
||||
// - json 表示卡片的 json 数据或者消息模板的 json 数据
|
||||
func FeiShuMessageWithInteractive(json string) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = json
|
||||
feishu.MsgType = FeiShuMsgTypeInteractive
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuMessageWithShareChat 飞书分享群名片
|
||||
// - chatId 群ID获取方式请参见群ID说明
|
||||
//
|
||||
// 群ID说明:https://open.feishu.cn/document/server-docs/group/chat/chat-id-description
|
||||
func FeiShuMessageWithShareChat(chatId string) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = struct {
|
||||
ChatID string `json:"chat_id"`
|
||||
}{chatId}
|
||||
feishu.MsgType = FeiShuMsgTypeShareChat
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuMessageWithShareUser 飞书分享个人名片
|
||||
// - userId 表示用户的 OpenID 获取方式请参见了解更多:如何获取 Open ID
|
||||
//
|
||||
// 如何获取 Open ID:https://open.feishu.cn/document/faq/trouble-shooting/how-to-obtain-openid
|
||||
func FeiShuMessageWithShareUser(userId string) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = struct {
|
||||
UserID string `json:"user_id"`
|
||||
}{userId}
|
||||
feishu.MsgType = FeiShuMsgTypeShareUser
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuMessageWithAudio 飞书语音消息
|
||||
// - fileKey 语音文件Key,可通过上传文件接口获取
|
||||
//
|
||||
// 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create
|
||||
func FeiShuMessageWithAudio(fileKey string) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = struct {
|
||||
FileKey string `json:"file_key"`
|
||||
}{fileKey}
|
||||
feishu.MsgType = FeiShuMsgTypeAudio
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuMessageWithMedia 飞书视频消息
|
||||
// - fileKey 视频文件Key,可通过上传文件接口获取
|
||||
//
|
||||
// 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create
|
||||
func FeiShuMessageWithMedia(fileKey string) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = struct {
|
||||
FileKey string `json:"file_key"`
|
||||
}{fileKey}
|
||||
feishu.MsgType = FeiShuMsgTypeMedia
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuMessageWithMediaAndCover 飞书带封面的视频消息
|
||||
// - fileKey 视频文件Key,可通过上传文件接口获取
|
||||
// - imageKey 图片文件Key,可通过上传文件接口获取
|
||||
//
|
||||
// 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create
|
||||
func FeiShuMessageWithMediaAndCover(fileKey, imageKey string) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = struct {
|
||||
FileKey string `json:"file_key"`
|
||||
ImageKey string `json:"image_key"`
|
||||
}{fileKey, imageKey}
|
||||
feishu.MsgType = FeiShuMsgTypeMedia
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuMessageWithFile 飞书文件消息
|
||||
// - fileKey 文件Key,可通过上传文件接口获取
|
||||
//
|
||||
// 上传文件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/file/create
|
||||
func FeiShuMessageWithFile(fileKey string) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = struct {
|
||||
FileKey string `json:"file_key"`
|
||||
}{fileKey}
|
||||
feishu.MsgType = FeiShuMsgTypeFile
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuMessageWithSticker 飞书表情包消息
|
||||
// - fileKey 表情包文件Key,目前仅支持发送机器人收到的表情包,可通过接收消息事件的推送获取表情包 file_key。
|
||||
//
|
||||
// 接收消息事件:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/events/receive
|
||||
func FeiShuMessageWithSticker(fileKey string) FeiShuMessage {
|
||||
return func(feishu *FeiShu) {
|
||||
feishu.Content = struct {
|
||||
FileKey string `json:"file_key"`
|
||||
}{fileKey}
|
||||
feishu.MsgType = FeiShuMsgTypeSticker
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package notifies
|
||||
|
||||
// NewFeiShuRichText 创建一个飞书富文本
|
||||
func NewFeiShuRichText() *FeiShuRichText {
|
||||
return &FeiShuRichText{
|
||||
content: map[string]*FeiShuRichTextContent{},
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShuRichText 飞书富文本结构
|
||||
type FeiShuRichText struct {
|
||||
content map[string]*FeiShuRichTextContent
|
||||
}
|
||||
|
||||
// Create 创建一个特定语言和标题的富文本内容
|
||||
func (slf *FeiShuRichText) Create(lang, title string) *FeiShuRichTextContent {
|
||||
content := &FeiShuRichTextContent{
|
||||
richText: slf,
|
||||
Title: title,
|
||||
Content: make([][]map[string]any, 1),
|
||||
}
|
||||
slf.content[lang] = content
|
||||
return content
|
||||
}
|
||||
|
||||
// FeiShuRichTextContent 飞书富文本内容体
|
||||
type FeiShuRichTextContent struct {
|
||||
richText *FeiShuRichText
|
||||
Title string `json:"title,omitempty"`
|
||||
Content [][]map[string]any `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
// AddText 添加文本
|
||||
func (slf *FeiShuRichTextContent) AddText(text string, styles ...string) *FeiShuRichTextContent {
|
||||
content := map[string]any{
|
||||
"tag": "text",
|
||||
"text": text,
|
||||
"style": styles,
|
||||
}
|
||||
slf.Content[0] = append(slf.Content[0], content)
|
||||
return slf
|
||||
}
|
||||
|
||||
// AddUnescapeText 添加 unescape 解码的文本
|
||||
func (slf *FeiShuRichTextContent) AddUnescapeText(text string, styles ...string) *FeiShuRichTextContent {
|
||||
content := map[string]any{
|
||||
"tag": "text",
|
||||
"text": text,
|
||||
"un_escape": true,
|
||||
"style": styles,
|
||||
}
|
||||
slf.Content[0] = append(slf.Content[0], content)
|
||||
return slf
|
||||
}
|
||||
|
||||
// AddLink 添加超链接文本
|
||||
// - 请确保链接地址的合法性,否则消息会发送失败
|
||||
func (slf *FeiShuRichTextContent) AddLink(text, href string, styles ...string) *FeiShuRichTextContent {
|
||||
content := map[string]any{
|
||||
"tag": "a",
|
||||
"text": text,
|
||||
"href": href,
|
||||
"style": styles,
|
||||
}
|
||||
slf.Content[0] = append(slf.Content[0], content)
|
||||
return slf
|
||||
}
|
||||
|
||||
// AddAt 添加@的用户
|
||||
// - @单个用户时,userId 字段必须是有效值
|
||||
// - @所有人填"all"。
|
||||
func (slf *FeiShuRichTextContent) AddAt(userId string, styles ...string) *FeiShuRichTextContent {
|
||||
content := map[string]any{
|
||||
"tag": "at",
|
||||
"user_id": userId,
|
||||
"style": styles,
|
||||
}
|
||||
slf.Content[0] = append(slf.Content[0], content)
|
||||
return slf
|
||||
}
|
||||
|
||||
// AddAtWithUsername 添加包含用户名的@用户
|
||||
// - @单个用户时,userId 字段必须是有效值
|
||||
// - @所有人填"all"。
|
||||
func (slf *FeiShuRichTextContent) AddAtWithUsername(userId, username string, styles ...string) *FeiShuRichTextContent {
|
||||
content := map[string]any{
|
||||
"tag": "at",
|
||||
"user_id": userId,
|
||||
"user_name": username,
|
||||
"style": styles,
|
||||
}
|
||||
slf.Content[0] = append(slf.Content[0], content)
|
||||
return slf
|
||||
}
|
||||
|
||||
// AddImg 添加图片
|
||||
// - imageKey 表示图片的唯一标识,可通过上传图片接口获取
|
||||
func (slf *FeiShuRichTextContent) AddImg(imageKey string) *FeiShuRichTextContent {
|
||||
content := map[string]any{
|
||||
"tag": "img",
|
||||
"image_key": imageKey,
|
||||
}
|
||||
slf.Content[0] = append(slf.Content[0], content)
|
||||
return slf
|
||||
}
|
||||
|
||||
// AddMedia 添加视频
|
||||
// - fileKey 表示视频文件的唯一标识,可通过上传文件接口获取
|
||||
func (slf *FeiShuRichTextContent) AddMedia(fileKey string) *FeiShuRichTextContent {
|
||||
content := map[string]any{
|
||||
"tag": "media",
|
||||
"file_key": fileKey,
|
||||
}
|
||||
slf.Content[0] = append(slf.Content[0], content)
|
||||
return slf
|
||||
}
|
||||
|
||||
// AddMediaWithCover 添加包含封面的视频
|
||||
// - fileKey 表示视频文件的唯一标识,可通过上传文件接口获取
|
||||
// - imageKey 表示图片的唯一标识,可通过上传图片接口获取
|
||||
func (slf *FeiShuRichTextContent) AddMediaWithCover(fileKey, imageKey string) *FeiShuRichTextContent {
|
||||
content := map[string]any{
|
||||
"tag": "media",
|
||||
"file_key": fileKey,
|
||||
"image_key": imageKey,
|
||||
}
|
||||
slf.Content[0] = append(slf.Content[0], content)
|
||||
return slf
|
||||
}
|
||||
|
||||
// AddEmotion 添加表情
|
||||
// - emojiType 表示表情类型,部分可选值请参见表情文案。
|
||||
//
|
||||
// 表情文案:https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce
|
||||
func (slf *FeiShuRichTextContent) AddEmotion(emojiType string) *FeiShuRichTextContent {
|
||||
content := map[string]any{
|
||||
"tag": "emotion",
|
||||
"emoji_type": emojiType,
|
||||
}
|
||||
slf.Content[0] = append(slf.Content[0], content)
|
||||
return slf
|
||||
}
|
||||
|
||||
// Ok 确认完成,将返回 FeiShuRichText 可继续创建多语言富文本
|
||||
func (slf *FeiShuRichTextContent) Ok() *FeiShuRichText {
|
||||
return slf.richText
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package notify
|
||||
|
||||
// Sender 通知发送器接口声明
|
||||
type Sender interface {
|
||||
// Push 推送通知
|
||||
Push(notify Notify) error
|
||||
}
|
||||
|
|
|
@ -1,40 +1,54 @@
|
|||
package senders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/kercylan98/minotaur/notify"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func NewFeiShu(webhook, receiveId string) *FeiShu {
|
||||
// NewFeiShu 根据特定的 webhook 地址创建飞书发送器
|
||||
func NewFeiShu(webhook string) *FeiShu {
|
||||
return &FeiShu{
|
||||
client: resty.New(),
|
||||
webhook: webhook,
|
||||
receiveId: receiveId,
|
||||
client: resty.New(),
|
||||
webhook: webhook,
|
||||
}
|
||||
}
|
||||
|
||||
// FeiShu 飞书发送器
|
||||
type FeiShu struct {
|
||||
client *resty.Client
|
||||
webhook string
|
||||
receiveId string
|
||||
client *resty.Client
|
||||
webhook string
|
||||
}
|
||||
|
||||
// Push 推送通知
|
||||
func (slf *FeiShu) Push(notify notify.Notify) error {
|
||||
content, err := notify.Format()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := slf.client.R().
|
||||
SetHeader("Content-Type", "application/json").
|
||||
SetHeader("Content-Type", "application/json; charset=utf-8").
|
||||
SetBody(content).
|
||||
Post(slf.webhook)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode() != http.StatusOK {
|
||||
return fmt.Errorf("FeiShu notify reader push failed, err: %s", resp.String())
|
||||
return fmt.Errorf("FeiShu notify push failed, err: %s", resp.String())
|
||||
}
|
||||
|
||||
var respStruct = struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data any `json:"data"`
|
||||
}{}
|
||||
if err := json.Unmarshal(resp.Body(), &respStruct); err != nil {
|
||||
return fmt.Errorf("FeiShu notify response unmarshal failed, err: %s", err)
|
||||
}
|
||||
if respStruct.Code != 0 {
|
||||
return fmt.Errorf("FeiShu notify push failed, err: [%d] %s", respStruct.Code, respStruct.Msg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package senders
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/notify/notifies"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFeiShu_Push(t *testing.T) {
|
||||
fs := NewFeiShu("https://open.feishu.cn/open-apis/bot/v2/hook/d886f30f-814c-47b1-aeb0-b508da0f7f22")
|
||||
|
||||
rt := notifies.NewFeiShu(notifies.FeiShuMessageWithRichText(notifies.NewFeiShuRichText().Create("zh_cn", "标题咯").AddText("哈哈哈").Ok()))
|
||||
if err := fs.Push(rt); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue