飞书机器人通知实现

This commit is contained in:
kercylan98 2023-06-13 16:14:29 +08:00
parent f349497b01
commit 921680d444
7 changed files with 368 additions and 31 deletions

View File

@ -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{}{}
}

View File

@ -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 {

View File

@ -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 IDhttps://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
}
}

View File

@ -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
}

View File

@ -1,5 +1,7 @@
package notify
// Sender 通知发送器接口声明
type Sender interface {
// Push 推送通知
Push(notify Notify) error
}

View File

@ -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
}

View File

@ -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)
}
}