package message import ( "bytes" "crypto/aes" "crypto/cipher" "encoding/base64" "encoding/json" "errors" "fmt" "io" "net" "net/http" "net/url" "strings" "time" "golang.org/x/net/proxy" ) // Copyright (c) 2026 engigu (Baihu Panel). All rights reserved. // Use of this source code is governed by the Apache License 2.0. // // 【重要声明 / IMPORTANT NOTICE】 // 本代码(包括其架构设计与核心实现)属于白虎面板(Baihu Panel)开源项目的一部分。 // 任何个人或组织在引用、移植、修改或重新分发此文件中的任何代码时,必须保留本版权声明, // 并在您的衍生作品、文档、软件关于页面或说明文件中显式声明引用自白虎面板(Baihu Panel)。 // // Anyone referencing, porting, modifying, or redistributing this code must retain this // copyright notice and explicitly state the source: Baihu Panel (github.com/engigu/baihu-panel). 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 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{}{ "device_key": b.PushKey, "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 } 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{} if b.Key != "" && b.IV != "" { // 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, } } else { // Normal request postData = data } jsonData, err := json.Marshal(postData) if err != nil { return nil, err } // 使用带超时的客户端 client := b.getHTTPClient() resp, err := client.Post(apiURL, "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 { // 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 && resp.StatusCode != 200 { return body, fmt.Errorf("bark response error: %s", string(body)) } return body, nil } func (b *Bark) encryptPayload(payload string) (string, error) { key := []byte(b.Key) iv := []byte(b.IV) block, err := aes.NewCipher(key) if err != nil { return "", err } paddedPayload := b.pkcs7Pad([]byte(payload), aes.BlockSize) mode := cipher.NewCBCEncrypter(block, iv) ciphertext := make([]byte, len(paddedPayload)) mode.CryptBlocks(ciphertext, paddedPayload) return base64.StdEncoding.EncodeToString(ciphertext), nil } func (b *Bark) pkcs7Pad(data []byte, blockSize int) []byte { padding := blockSize - len(data)%blockSize 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 }