chore: udpate bark push #83

This commit is contained in:
duorameng
2026-04-15 17:09:45 +08:00
parent 39290b4edd
commit e6864373bc
3 changed files with 131 additions and 41 deletions
+122 -40
View File
@@ -6,10 +6,16 @@ import (
"crypto/cipher" "crypto/cipher"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net"
"net/http" "net/http"
"net/url"
"strings" "strings"
"time"
"golang.org/x/net/proxy"
) )
type barkResponse struct { type barkResponse struct {
@@ -18,22 +24,27 @@ type barkResponse struct {
} }
type Bark struct { type Bark struct {
PushKey string PushKey string
Archive string Archive string
Group string Group string
Sound string Sound string
Icon string Icon string
Level string Level string
URL string URL string
Key string Key string
IV string IV string
Server string Server string
Badge string
Copy string
AutoCopy string
ProxyURL string // 可选的代理地址
} }
func (b *Bark) Request(title, content string) ([]byte, error) { func (b *Bark) Request(title, content string) ([]byte, error) {
data := map[string]interface{}{ data := map[string]interface{}{
"title": title, "device_key": b.PushKey,
"body": content, "title": title,
"body": content,
} }
if b.Archive != "" { if b.Archive != "" {
data["isArchive"] = b.Archive data["isArchive"] = b.Archive
@@ -53,32 +64,53 @@ func (b *Bark) Request(title, content string) ([]byte, error) {
if b.URL != "" { if b.URL != "" {
data["url"] = b.URL data["url"] = b.URL
} }
if b.Badge != "" {
data["badge"] = b.Badge
}
if b.Copy != "" {
data["copy"] = b.Copy
}
if b.AutoCopy != "" {
data["autoCopy"] = b.AutoCopy
}
server := b.Server
if server == "" {
server = "https://api.day.app"
}
server = strings.TrimSuffix(server, "/")
apiURL := server + "/push"
// If PushKey is a full URL, we might be using an old-style custom URL
if strings.HasPrefix(b.PushKey, "http") {
apiURL = b.PushKey
}
var postData interface{} var postData interface{}
url := b.getURL()
if b.Key != "" && b.IV != "" { if b.Key != "" && b.IV != "" {
// Use encryption // Encrypted Request
jsonData, err := json.Marshal(data) // 1. Prepare the full notification payload (without device_key, as specified for encryption)
encryptData := make(map[string]interface{})
for k, v := range data {
if k != "device_key" {
encryptData[k] = v
}
}
jsonData, err := json.Marshal(encryptData)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ciphertext, err := b.encryptPayload(string(jsonData)) ciphertext, err := b.encryptPayload(string(jsonData))
if err != nil { if err != nil {
return nil, fmt.Errorf("encryption failed: %v", err) return nil, fmt.Errorf("encryption failed: %v", err)
} }
postData = map[string]interface{}{ postData = map[string]interface{}{
"ciphertext": ciphertext, "ciphertext": ciphertext,
"iv": b.IV,
"device_key": b.PushKey, "device_key": b.PushKey,
"sound": b.Sound,
}
// When using encryption, use the push endpoint if PushKey is just a key
if !strings.HasPrefix(b.PushKey, "http") {
server := b.Server
if server == "" {
server = "https://api.day.app"
}
url = strings.TrimSuffix(server, "/") + "/push"
} }
} else { } else {
// Normal request // Normal request
@@ -90,7 +122,9 @@ func (b *Bark) Request(title, content string) ([]byte, error) {
return nil, err return nil, err
} }
resp, err := http.Post(url, "application/json;charset=utf-8", bytes.NewBuffer(jsonData)) // 使用带超时的客户端
client := b.getHTTPClient()
resp, err := client.Post(apiURL, "application/json;charset=utf-8", bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -104,28 +138,19 @@ func (b *Bark) Request(title, content string) ([]byte, error) {
var r barkResponse var r barkResponse
err = json.Unmarshal(body, &r) err = json.Unmarshal(body, &r)
if err != nil { if err != nil {
// If not JSON, return the raw body as it might be a simple success message from some servers
if resp.StatusCode == 200 {
return body, nil
}
return body, err return body, err
} }
if r.Code != 200 { if r.Code != 200 && resp.StatusCode != 200 {
return body, fmt.Errorf("bark response error: %s", string(body)) return body, fmt.Errorf("bark response error: %s", string(body))
} }
return body, nil return body, nil
} }
func (b *Bark) getURL() string {
pushKey := b.PushKey
if strings.HasPrefix(pushKey, "http") {
return pushKey
}
server := b.Server
if server == "" {
server = "https://api.day.app"
}
server = strings.TrimSuffix(server, "/")
return fmt.Sprintf("%s/%s", server, pushKey)
}
func (b *Bark) encryptPayload(payload string) (string, error) { func (b *Bark) encryptPayload(payload string) (string, error) {
key := []byte(b.Key) key := []byte(b.Key)
iv := []byte(b.IV) iv := []byte(b.IV)
@@ -148,3 +173,60 @@ func (b *Bark) pkcs7Pad(data []byte, blockSize int) []byte {
padtext := bytes.Repeat([]byte{byte(padding)}, padding) padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padtext...) return append(data, padtext...)
} }
// getHTTPClient 获取 HTTP 客户端(含超时和代理)
func (b *Bark) getHTTPClient() *http.Client {
client := &http.Client{
Timeout: 20 * time.Second,
}
if b.ProxyURL != "" {
proxyURL, err := url.Parse(b.ProxyURL)
if err == nil {
if strings.HasPrefix(strings.ToLower(b.ProxyURL), "socks5://") {
dialer, err := b.createSOCKS5Dialer(proxyURL)
if err == nil {
client.Transport = &http.Transport{
DialContext: dialer.DialContext,
}
}
} else {
client.Transport = &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
}
}
}
return client
}
// createSOCKS5Dialer 创建 SOCKS5 拨号器
func (b *Bark) createSOCKS5Dialer(proxyURL *url.URL) (proxy.ContextDialer, error) {
host := proxyURL.Host
var auth *proxy.Auth
if proxyURL.User != nil {
password, _ := proxyURL.User.Password()
auth = &proxy.Auth{
User: proxyURL.User.Username(),
Password: password,
}
}
baseDialer := &net.Dialer{
Timeout: 20 * time.Second,
KeepAlive: 20 * time.Second,
}
dialer, err := proxy.SOCKS5("tcp", host, auth, baseDialer)
if err != nil {
return nil, err
}
contextDialer, ok := dialer.(proxy.ContextDialer)
if !ok {
return nil, errors.New("failed to convert to ContextDialer")
}
return contextDialer, nil
}
+5 -1
View File
@@ -24,7 +24,11 @@ func (c *BarkChannel) Send(config ChannelConfig, msg *Message) (*Result, error)
URL: config.GetString("url"), URL: config.GetString("url"),
Key: config.GetString("key"), Key: config.GetString("key"),
IV: config.GetString("iv"), IV: config.GetString("iv"),
Server: config.GetString("server"), Server: config.GetString("server"),
Badge: config.GetString("badge"),
Copy: config.GetString("copy"),
AutoCopy: config.GetString("auto_copy"),
ProxyURL: config.GetString("proxy_url"),
} }
res, err := cli.Request(msg.Title, msg.Text) res, err := cli.Request(msg.Title, msg.Text)
+4
View File
@@ -51,11 +51,15 @@ const channelConfigFields: Record<string, { key: string; label: string; required
Bark: [ Bark: [
{ key: 'server', label: '服务地址', required: false, placeholder: '默认 https://api.day.app' }, { key: 'server', label: '服务地址', required: false, placeholder: '默认 https://api.day.app' },
{ key: 'push_key', label: 'Push Key', required: true, placeholder: 'Bark Push Key' }, { key: 'push_key', label: 'Push Key', required: true, placeholder: 'Bark Push Key' },
{ key: 'proxy_url', label: '代理地址', required: false, placeholder: 'http/https/socks5 代理' },
{ key: 'sound', label: '推送声音', required: false, placeholder: '留空使用默认' }, { key: 'sound', label: '推送声音', required: false, placeholder: '留空使用默认' },
{ key: 'badge', label: '角标数量', required: false, placeholder: '例如 1' },
{ key: 'group', label: '推送分组', required: false }, { key: 'group', label: '推送分组', required: false },
{ key: 'icon', label: '推送图标', required: false, placeholder: '图标 URL' }, { key: 'icon', label: '推送图标', required: false, placeholder: '图标 URL' },
{ key: 'level', label: '时效性', required: false, placeholder: 'active / timeSensitive / passive' }, { key: 'level', label: '时效性', required: false, placeholder: 'active / timeSensitive / passive' },
{ key: 'url', label: '跳转URL', required: false }, { key: 'url', label: '跳转URL', required: false },
{ key: 'copy', label: '复制内容', required: false, placeholder: '收到推送时自动复制的内容' },
{ key: 'auto_copy', label: '自动复制', required: false, placeholder: '1 表示开启' },
], ],
Dtalk: [ Dtalk: [
{ key: 'access_token', label: 'Access Token', required: true, placeholder: '钉钉机器人 access_token' }, { key: 'access_token', label: 'Access Token', required: true, placeholder: '钉钉机器人 access_token' },