mirror of
https://github.com/lone-cloud/prism
synced 2026-06-03 08:43:10 -07:00
131 lines
2.8 KiB
Go
131 lines
2.8 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"mime"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"prism/service/delivery"
|
|
"prism/service/util"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
func (s *Server) handleNtfyPublish(w http.ResponseWriter, r *http.Request) {
|
|
appName := chi.URLParam(r, "appName")
|
|
decodedAppName, err := url.PathUnescape(appName)
|
|
if err != nil {
|
|
util.JSONError(w, "Invalid app name", http.StatusBadRequest)
|
|
return
|
|
}
|
|
appName = decodedAppName
|
|
if appName == "" || strings.Contains(appName, "/") {
|
|
util.JSONError(w, "Invalid app name", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
util.JSONError(w, "Failed to read body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
message, title := parseNtfyPayload(r, body)
|
|
|
|
if message == "" {
|
|
util.JSONError(w, "Message required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if title == appName {
|
|
title = ""
|
|
}
|
|
|
|
notif := delivery.Notification{
|
|
Title: title,
|
|
Message: message,
|
|
}
|
|
|
|
if err := s.publisher.Publish(appName, notif); err != nil {
|
|
util.LogAndError(w, s.logger, "Failed to send notification", http.StatusInternalServerError, err, "app", appName)
|
|
return
|
|
}
|
|
|
|
s.logger.Debug("Sent ntfy message", "app", appName, "title", title, "preview", truncate(message, 50))
|
|
|
|
now := time.Now()
|
|
w.Header().Set("Content-Type", "application/json")
|
|
response := map[string]any{
|
|
"id": now.UnixNano(),
|
|
"time": now.Unix(),
|
|
"event": "message",
|
|
"topic": appName,
|
|
"message": message,
|
|
}
|
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
s.logger.Error("Failed to encode response", "error", err)
|
|
}
|
|
}
|
|
|
|
func parseNtfyPayload(r *http.Request, body []byte) (string, string) {
|
|
var message, title string
|
|
|
|
mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
if err != nil {
|
|
mediaType = ""
|
|
}
|
|
|
|
switch mediaType {
|
|
case "application/json":
|
|
var payload struct {
|
|
Title string `json:"title"`
|
|
Message string `json:"message"`
|
|
}
|
|
if err := json.Unmarshal(body, &payload); err == nil {
|
|
message = payload.Message
|
|
title = payload.Title
|
|
} else {
|
|
message = string(body)
|
|
}
|
|
case "application/x-www-form-urlencoded":
|
|
r.Body = io.NopCloser(bytes.NewReader(body))
|
|
if err := r.ParseForm(); err == nil {
|
|
message = r.PostForm.Get("message")
|
|
if message == "" {
|
|
message = string(body)
|
|
}
|
|
title = firstNonEmpty(r.PostForm.Get("title"), r.PostForm.Get("t"))
|
|
} else {
|
|
message = string(body)
|
|
}
|
|
default:
|
|
message = string(body)
|
|
}
|
|
|
|
if title == "" {
|
|
title = firstNonEmpty(r.Header.Get("X-Title"), r.Header.Get("Title"), r.Header.Get("t"))
|
|
}
|
|
|
|
return message, title
|
|
}
|
|
|
|
func firstNonEmpty(values ...string) string {
|
|
for _, value := range values {
|
|
if value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func truncate(s string, max int) string {
|
|
if len(s) <= max {
|
|
return s
|
|
}
|
|
return s[:max]
|
|
}
|