package server import ( "context" "fmt" "log" "mime" "net/http" "strings" "time" ) const ( settingAdminUser = "admin_user" settingAdminHash = "admin_hash" settingNtfyURL = "ntfy_url" settingNtfyAuth = "ntfy_auth" // токен tk_... или "user:password" settingNotifySuccess = "notify_success" ) // notify отправляет уведомление в ntfy (асинхронно, ошибки только в лог). func (s *Server) notify(title, msg, priority, tags string) { url := s.st.Setting(settingNtfyURL) if url == "" { return } auth := s.st.Setting(settingNtfyAuth) go func() { if err := sendNtfy(url, auth, title, msg, priority, tags); err != nil { log.Printf("ntfy: %v", err) } }() } func sendNtfy(url, auth, title, msg, priority, tags string) error { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(msg)) if err != nil { return err } // заголовки должны быть ASCII — кириллицу кодируем по RFC 2047, ntfy это понимает req.Header.Set("Title", mime.BEncoding.Encode("UTF-8", title)) if priority != "" { req.Header.Set("Priority", priority) } if tags != "" { req.Header.Set("Tags", tags) } switch { case auth == "": case strings.Contains(auth, ":"): u, p, _ := strings.Cut(auth, ":") req.SetBasicAuth(u, p) default: req.Header.Set("Authorization", "Bearer "+auth) } resp, err := http.DefaultClient.Do(req) if err != nil { return err } resp.Body.Close() if resp.StatusCode >= 300 { return fmt.Errorf("ответ %s", resp.Status) } return nil } func humanBytes(n int64) string { const unit = 1024 if n < unit { return fmt.Sprintf("%d Б", n) } div, exp := int64(unit), 0 for m := n / unit; m >= unit; m /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %cБ", float64(n)/float64(div), []rune("КМГТП")[exp]) }