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"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"time"
"golang.org/x/net/proxy"
)
type barkResponse struct {
@@ -18,22 +24,27 @@ type barkResponse struct {
}
type Bark struct {
PushKey string
Archive string
Group string
Sound string
Icon string
Level string
URL string
Key string
IV string
Server string
PushKey string
Archive string
Group string
Sound string
Icon string
Level string
URL string
Key string
IV string
Server string
Badge string
Copy string
AutoCopy string
ProxyURL string // 可选的代理地址
}
func (b *Bark) Request(title, content string) ([]byte, error) {
data := map[string]interface{}{
"title": title,
"body": content,
"device_key": b.PushKey,
"title": title,
"body": content,
}
if b.Archive != "" {
data["isArchive"] = b.Archive
@@ -53,32 +64,53 @@ func (b *Bark) Request(title, content string) ([]byte, error) {
if 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{}
url := b.getURL()
if b.Key != "" && b.IV != "" {
// Use encryption
jsonData, err := json.Marshal(data)
// Encrypted Request
// 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 {
return nil, err
}
ciphertext, err := b.encryptPayload(string(jsonData))
if err != nil {
return nil, fmt.Errorf("encryption failed: %v", err)
}
postData = map[string]interface{}{
"ciphertext": ciphertext,
"iv": b.IV,
"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 {
// Normal request
@@ -90,7 +122,9 @@ func (b *Bark) Request(title, content string) ([]byte, error) {
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 {
return nil, err
}
@@ -104,28 +138,19 @@ func (b *Bark) Request(title, content string) ([]byte, error) {
var r barkResponse
err = json.Unmarshal(body, &r)
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
}
if r.Code != 200 {
if r.Code != 200 && resp.StatusCode != 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
}
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) {
key := []byte(b.Key)
iv := []byte(b.IV)
@@ -148,3 +173,60 @@ func (b *Bark) pkcs7Pad(data []byte, blockSize int) []byte {
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
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"),
Key: config.GetString("key"),
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)
+4
View File
@@ -51,11 +51,15 @@ const channelConfigFields: Record<string, { key: string; label: string; required
Bark: [
{ key: 'server', label: '服务地址', required: false, placeholder: '默认 https://api.day.app' },
{ 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: 'badge', label: '角标数量', required: false, placeholder: '例如 1' },
{ key: 'group', label: '推送分组', required: false },
{ key: 'icon', label: '推送图标', required: false, placeholder: '图标 URL' },
{ key: 'level', label: '时效性', required: false, placeholder: 'active / timeSensitive / passive' },
{ key: 'url', label: '跳转URL', required: false },
{ key: 'copy', label: '复制内容', required: false, placeholder: '收到推送时自动复制的内容' },
{ key: 'auto_copy', label: '自动复制', required: false, placeholder: '1 表示开启' },
],
Dtalk: [
{ key: 'access_token', label: 'Access Token', required: true, placeholder: '钉钉机器人 access_token' },