package bark import ( "bytes" "context" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "time" "github.com/pkg/errors" ) // Service allow you to configure Bark service. type Service struct { deviceKey string client *http.Client serverURLs []string } func defaultHTTPClient() *http.Client { return &http.Client{ Timeout: 5 * time.Second, } } // DefaultServerURL is the default server to use for the bark service. const DefaultServerURL = "https://api.day.app/" // normalizeServerURL normalizes the server URL. It prefixes it with https:// if it's not already and appends a slash // if it's not already there. If the serverURL is empty, the DefaultServerURL is used. We're not validating the url here // on purpose, we leave that to the http client. func normalizeServerURL(serverURL string) string { if serverURL == "" { return DefaultServerURL } // Normalize the url if !strings.HasPrefix(serverURL, "http") { serverURL = "https://" + serverURL } if !strings.HasSuffix(serverURL, "/") { serverURL = serverURL + "/" } return serverURL } // AddReceivers adds server URLs to the list of servers to use for sending messages. We call it Receivers and not // servers because strictly speaking, the server is still receiving the message, and additionally we're following the // naming convention of the other services. func (s *Service) AddReceivers(serverURLs ...string) { for _, serverURL := range serverURLs { serverURL = normalizeServerURL(serverURL) s.serverURLs = append(s.serverURLs, serverURL) } } // NewWithServers returns a new instance of Bark service. You can use this service to send messages to bark. You can // specify the servers to send the messages to. By default, the service will use the default server // (https://api.day.app/) if you don't specify any servers. func NewWithServers(deviceKey string, serverURLs ...string) *Service { s := &Service{ deviceKey: deviceKey, client: defaultHTTPClient(), } if len(serverURLs) == 0 { serverURLs = append(serverURLs, DefaultServerURL) } // Calling service.AddReceivers() instead of directly setting the serverURLs because we want to normalize the URLs. s.AddReceivers(serverURLs...) return s } // New returns a new instance of Bark service. You can use this service to send messages to bark. By default, the // service will use the default server (https://api.day.app/). func New(deviceKey string) *Service { return NewWithServers(deviceKey) } // postData is the data to send to the bark server. type postData struct { DeviceKey string `json:"device_key"` Title string `json:"title"` Body string `json:"body,omitempty"` Badge int `json:"badge,omitempty"` Sound string `json:"sound,omitempty"` Icon string `json:"icon,omitempty"` Group string `json:"group,omitempty"` URL string `json:"pushURL,omitempty"` } func (s *Service) send(ctx context.Context, serverURL, subject, content string) (err error) { if serverURL == "" { return errors.New("server url is empty") } // Marshal the message to post message := &postData{ DeviceKey: s.deviceKey, Title: subject, Body: content, Sound: "alarm.caf", } messageJSON, err := json.Marshal(message) if err != nil { return errors.Wrap(err, "marshal message") } pushURL := serverURL + "push" // Create new request req, err := http.NewRequestWithContext(ctx, http.MethodPost, pushURL, bytes.NewBuffer(messageJSON)) if err != nil { return errors.Wrap(err, "create request") } req.Header.Set("Content-Type", "application/json; charset=utf-8") // Send request resp, err := s.client.Do(req) if err != nil { return errors.Wrap(err, "send request") } defer func() { _ = resp.Body.Close() }() // Read response and verify success result, err := ioutil.ReadAll(resp.Body) if err != nil { return errors.Wrap(err, "read response") } if resp.StatusCode != http.StatusOK { return fmt.Errorf("bark returned status code %d: %s", resp.StatusCode, string(result)) } return nil } // Send takes a message subject and a message content and sends them to bark application. func (s *Service) Send(ctx context.Context, subject, content string) error { if s.client == nil { return errors.New("client is nil") } for _, serverURL := range s.serverURLs { select { case <-ctx.Done(): return ctx.Err() default: err := s.send(ctx, serverURL, subject, content) if err != nil { return errors.Wrapf(err, "failed to send message to bark server %q", serverURL) } } } return nil }