From 8279d27e3103aca68503872a57f6d32f54ec9d7a Mon Sep 17 00:00:00 2001 From: engigu Date: Wed, 21 Jan 2026 19:26:48 +0800 Subject: [PATCH] feat: add bark message --- models/send_ins.go | 4 + pkg/constant/constant.go | 1 + pkg/message/bark.go | 86 +++++++++++++++++++ service/send_ins_service/send_ins.go | 4 + .../unified/channel_registry.go | 6 +- .../unified/channels/bark.go | 47 ++++++++++ .../unified/channels/types.go | 1 + service/send_way_service/send_way.go | 41 +++++++++ web/src/constant.js | 25 +++++- 9 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 pkg/message/bark.go create mode 100644 service/send_message_service/unified/channels/bark.go diff --git a/models/send_ins.go b/models/send_ins.go index db3afdd..ffd3018 100644 --- a/models/send_ins.go +++ b/models/send_ins.go @@ -55,6 +55,10 @@ type InsAliyunSMSConfig struct { type InsTelegramConfig struct { } +// InsBarkConfig 实例里面的Bark config +type InsBarkConfig struct { +} + // ManyAddTaskIns 批量添加实例 func ManyAddTaskIns(taskIns []SendTasksIns) error { tx := db.Begin() diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index 1d98002..3b16443 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -65,6 +65,7 @@ const ( MessageTypeMessageNest = "MessageNest" MessageTypeAliyunSMS = "AliyunSMS" MessageTypeTelegram = "Telegram" + MessageTypeBark = "Bark" ) // 限制goroutine的最大数量 diff --git a/pkg/message/bark.go b/pkg/message/bark.go new file mode 100644 index 0000000..0458aab --- /dev/null +++ b/pkg/message/bark.go @@ -0,0 +1,86 @@ +package message + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" +) + +type barkResponse struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type Bark struct { + PushKey string + Archive string + Group string + Sound string + Icon string + Level string + URL string +} + +func (b *Bark) Request(title, content string) ([]byte, error) { + url := b.getURL() + data := map[string]interface{}{ + "title": title, + "body": content, + } + if b.Archive != "" { + data["isArchive"] = b.Archive + } + if b.Group != "" { + data["group"] = b.Group + } + if b.Sound != "" { + data["sound"] = b.Sound + } + if b.Icon != "" { + data["icon"] = b.Icon + } + if b.Level != "" { + data["level"] = b.Level + } + if b.URL != "" { + data["url"] = b.URL + } + + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + + resp, err := http.Post(url, "application/json;charset=utf-8", bytes.NewBuffer(jsonData)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var r barkResponse + err = json.Unmarshal(body, &r) + if err != nil { + return body, err + } + + if r.Code != 200 { + return body, fmt.Errorf("bark response error: %s", string(body)) + } + return body, nil +} + +func (b *Bark) getURL() string { + pushKey := b.PushKey + if strings.HasPrefix(pushKey, "http") { + return pushKey + } + return fmt.Sprintf("https://api.day.app/%s", pushKey) +} diff --git a/service/send_ins_service/send_ins.go b/service/send_ins_service/send_ins.go index ec2832d..442e20f 100644 --- a/service/send_ins_service/send_ins.go +++ b/service/send_ins_service/send_ins.go @@ -127,6 +127,10 @@ func (sw *SendTaskInsService) ValidateDiffIns(ins models.SendTasksIns) (string, var Config models.InsTelegramConfig return "", Config } + if ins.WayType == constant.MessageTypeBark { + var Config models.InsBarkConfig + return "", Config + } return "未知的渠道的config校验", empty } diff --git a/service/send_message_service/unified/channel_registry.go b/service/send_message_service/unified/channel_registry.go index f612624..8973934 100644 --- a/service/send_message_service/unified/channel_registry.go +++ b/service/send_message_service/unified/channel_registry.go @@ -8,8 +8,8 @@ import ( // 重导出channels包的类型,方便外部使用 type ( - Channel = channels.Channel - UnifiedMessageContent = channels.UnifiedMessageContent + Channel = channels.Channel + UnifiedMessageContent = channels.UnifiedMessageContent ) // 重导出常量 @@ -27,6 +27,7 @@ const ( MessageTypeMessageNest = channels.MessageTypeMessageNest MessageTypeAliyunSMS = channels.MessageTypeAliyunSMS MessageTypeTelegram = channels.MessageTypeTelegram + MessageTypeBark = channels.MessageTypeBark ) // ChannelRegistry 渠道注册表 @@ -101,6 +102,7 @@ func GetGlobalChannelRegistry() *ChannelRegistry { globalChannelRegistry.Register(channels.NewMessageNestChannel()) globalChannelRegistry.Register(channels.NewAliyunSMSChannel()) globalChannelRegistry.Register(channels.NewTelegramChannel()) + globalChannelRegistry.Register(channels.NewBarkChannel()) }) return globalChannelRegistry } diff --git a/service/send_message_service/unified/channels/bark.go b/service/send_message_service/unified/channels/bark.go new file mode 100644 index 0000000..dbae221 --- /dev/null +++ b/service/send_message_service/unified/channels/bark.go @@ -0,0 +1,47 @@ +package channels + +import ( + "fmt" + "message-nest/models" + "message-nest/pkg/message" + "message-nest/service/send_ins_service" + "message-nest/service/send_way_service" +) + +type BarkChannel struct{ *BaseChannel } + +func NewBarkChannel() *BarkChannel { + return &BarkChannel{BaseChannel: NewBaseChannel(MessageTypeBark, []string{FormatTypeText})} +} + +func (c *BarkChannel) SendUnified(msgObj interface{}, ins models.SendTasksIns, content *UnifiedMessageContent) (string, string) { + auth, ok := msgObj.(*send_way_service.WayDetailBark) + if !ok { + return "", "类型转换失败" + } + insService := send_ins_service.SendTaskInsService{} + errStr, configInterface := insService.ValidateDiffIns(ins) + if errStr != "" { + return errStr, "" + } + _, ok = configInterface.(models.InsBarkConfig) + if !ok { + return "Bark config校验失败", "" + } + + cli := message.Bark{ + PushKey: auth.PushKey, + Archive: auth.Archive, + Group: auth.Group, + Sound: auth.Sound, + Icon: auth.Icon, + Level: auth.Level, + URL: auth.URL, + } + + res, err := cli.Request(content.Title, content.Text) + if err != nil { + return string(res), fmt.Sprintf("发送失败:%s", err.Error()) + } + return string(res), "" +} diff --git a/service/send_message_service/unified/channels/types.go b/service/send_message_service/unified/channels/types.go index 3d11fab..83d65c9 100644 --- a/service/send_message_service/unified/channels/types.go +++ b/service/send_message_service/unified/channels/types.go @@ -19,6 +19,7 @@ const ( MessageTypeMessageNest = constant.MessageTypeMessageNest MessageTypeAliyunSMS = constant.MessageTypeAliyunSMS MessageTypeTelegram = constant.MessageTypeTelegram + MessageTypeBark = constant.MessageTypeBark ) // UnifiedMessageContent 统一的消息内容结构 diff --git a/service/send_way_service/send_way.go b/service/send_way_service/send_way.go index 85f5d38..8809e07 100644 --- a/service/send_way_service/send_way.go +++ b/service/send_way_service/send_way.go @@ -46,6 +46,7 @@ var ( constant.MessageTypeMessageNest: func() WayValidator { return &MessageNest{} }, constant.MessageTypeAliyunSMS: func() WayValidator { return &WayDetailAliyunSMS{} }, constant.MessageTypeTelegram: func() WayValidator { return &WayDetailTelegram{} }, + constant.MessageTypeBark: func() WayValidator { return &WayDetailBark{} }, } testerRegistry = map[string]func(interface{}) WayTester{ constant.MessageTypeEmail: func(m interface{}) WayTester { return m.(*WayDetailEmail) }, @@ -57,6 +58,7 @@ var ( constant.MessageTypeMessageNest: func(m interface{}) WayTester { return m.(*MessageNest) }, constant.MessageTypeAliyunSMS: func(m interface{}) WayTester { return m.(*WayDetailAliyunSMS) }, constant.MessageTypeTelegram: func(m interface{}) WayTester { return m.(*WayDetailTelegram) }, + constant.MessageTypeBark: func(m interface{}) WayTester { return m.(*WayDetailBark) }, } ) @@ -286,6 +288,45 @@ func (w *WayDetailTelegram) Test() (string, string) { return "", string(res) } +// WayDetailBark Bark渠道明细字段 +type WayDetailBark struct { + PushKey string `json:"push_key" validate:"required,max=200" label:"Bark Push Key"` + Archive string `json:"archive" validate:"max=10" label:"推送是否存档"` + Group string `json:"group" validate:"max=50" label:"推送分组"` + Sound string `json:"sound" validate:"max=50" label:"推送声音"` + Icon string `json:"icon" validate:"max=200" label:"推送图标"` + Level string `json:"level" validate:"max=20" label:"推送时效性"` + URL string `json:"url" validate:"max=200" label:"推送跳转URL"` +} + +func (w *WayDetailBark) Validate(authJson string) (string, interface{}) { + var empty interface{} + err := json.Unmarshal([]byte(authJson), w) + if err != nil { + return "Bark参数反序列化失败!", empty + } + _, msg := app.CommonPlaygroundValid(*w) + return msg, w +} + +func (w *WayDetailBark) Test() (string, string) { + testMsg := "This is a test message from message-nest." + var cli = message.Bark{ + PushKey: w.PushKey, + Archive: w.Archive, + Group: w.Group, + Sound: w.Sound, + Icon: w.Icon, + Level: w.Level, + URL: w.URL, + } + res, err := cli.Request("Test Message", testMsg) + if err != nil { + return fmt.Sprintf("发送失败:%s", err), string(res) + } + return "", string(res) +} + func (sw *SendWay) GetByID() (interface{}, error) { return models.GetWayByID(sw.ID) } diff --git a/web/src/constant.js b/web/src/constant.js index 17cf74c..289e71b 100644 --- a/web/src/constant.js +++ b/web/src/constant.js @@ -191,7 +191,7 @@ const CONSTANT = { { subLabel: '渠道名', value: '', col: 'name', desc: "想要设置的渠道名字" }, ], tips: { - text: "Telegram机器人说明", + text: "Telegram机器人说明", desc: "使用Telegram Bot发送消息。
1. 通过 @BotFather 创建机器人获取Bot Token
2. Chat ID可以是用户ID、群组ID或频道ID
3. 代理配置说明:
  • 自定义API地址(优先级最高):适用于自建代理服务器,如 https://api.example.com
  • 代理地址(优先级较低):支持以下格式
    - HTTP代理:http://127.0.0.1:7890
    - HTTPS代理:https://proxy.example.com:8080
    - SOCKS5代理:socks5://127.0.0.1:1080
    - 带认证的SOCKS5:socks5://user:pass@host:1080
  • 如果同时配置,将优先使用自定义API地址,代理地址会被忽略" }, taskInsRadios: [ @@ -202,6 +202,29 @@ const CONSTANT = { taskInsInputs: [ ], }, + { + type: 'Bark', + label: 'Bark推送', + inputs: [ + { subLabel: 'Push Key', value: '', col: 'push_key', desc: "Bark设备码或自建IP,例:DxHcxxxxxRxxxxxxcm" }, + { subLabel: '存档', value: '', col: 'archive', desc: "1(存档)或 0(不存档),可选" }, + { subLabel: '分组', value: '', col: 'group', desc: "推送分组,可选" }, + { subLabel: '推送声音', value: '', col: 'sound', desc: "推送铃声,可选" }, + { subLabel: '推送图标', value: '', col: 'icon', desc: "推送图标URL,可选" }, + { subLabel: '推送时效', value: '', col: 'level', desc: "active/timeSensitive/passive,可选" }, + { subLabel: '跳转URL', value: '', col: 'url', desc: "点击推送跳转的URL,可选" }, + { subLabel: '渠道名', value: '', col: 'name', desc: "想要设置的渠道名字" }, + ], + tips: { + text: "Bark推送说明", + desc: "Bark 是一款 iOS 专用的推送软件。
1. 在 iPhone 上安装 Bark App
2. 复制你的设备 Key 或自建服务器地址
3. 详细参数说明可以查看 Bark 官方文档" + }, + taskInsRadios: [ + { subLabel: 'text', content: 'text' }, + ], + taskInsInputs: [ + ], + }, ], API_VIEW_DATA: [ { label: "curl", class: "language-shell line-numbers", code: "", func: ApiStrGenerate.getCurlString },