Explorar el Código

Merge branch 'binwiederhier:main' into main

timof hace 7 meses
padre
commit
d9ecee7200
Se han modificado 4 ficheros con 97 adiciones y 10 borrados
  1. 1 0
      docs/integrations.md
  2. 9 5
      server/server.go
  3. 55 0
      server/server_test.go
  4. 32 5
      server/types.go

+ 1 - 0
docs/integrations.md

@@ -96,6 +96,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
 - [Ntfy_CSV_Reminders](https://github.com/thiswillbeyourgithub/Ntfy_CSV_Reminders) - A Python tool that sends random-timing phone notifications for recurring tasks by using daily probability checks based on CSV-defined frequencies.
 - [Daily Fact Ntfy](https://github.com/thiswillbeyourgithub/Daily_Fact_Ntfy) - Generate [llm](https://github.com/simonw/llm) generated fact every day about any topic you're interested in.
 - [ntfyexec](https://github.com/alecthomas/ntfyexec) - Send a notification through ntfy.sh if a command fails
+- [Ntfy Desktop](https://github.com/emmaexe/ntfyDesktop) - Fully featured desktop client for Linux, built with Qt and C++.
 
 ## Projects + scripts 
 

+ 9 - 5
server/server.go

@@ -991,7 +991,12 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
 	} else if call != "" && !isBoolValue(call) && !phoneNumberRegex.MatchString(call) {
 		return false, false, "", "", "", false, errHTTPBadRequestPhoneNumberInvalid
 	}
-	messageStr := strings.ReplaceAll(readParam(r, "x-message", "message", "m"), "\\n", "\n")
+	template = templateMode(readParam(r, "x-template", "template", "tpl"))
+	messageStr := readParam(r, "x-message", "message", "m")
+	if !template.InlineMode() {
+		// Convert "\n" to literal newline everything but inline mode
+		messageStr = strings.ReplaceAll(messageStr, "\\n", "\n")
+	}
 	if messageStr != "" {
 		m.Message = messageStr
 	}
@@ -1033,7 +1038,6 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
 	if markdown || strings.ToLower(contentType) == "text/markdown" {
 		m.ContentType = "text/markdown"
 	}
-	template = templateMode(readParam(r, "x-template", "template", "tpl"))
 	unifiedpush = readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see GET too!
 	contentEncoding := readParam(r, "content-encoding")
 	if unifiedpush || contentEncoding == "aes128gcm" {
@@ -1119,8 +1123,8 @@ func (s *Server) handleBodyAsTemplatedTextMessage(m *message, template templateM
 		return errHTTPEntityTooLargeJSONBody
 	}
 	peekedBody := strings.TrimSpace(string(body.PeekedBytes))
-	if templateName := template.Name(); templateName != "" {
-		if err := s.renderTemplateFromFile(m, templateName, peekedBody); err != nil {
+	if template.FileMode() {
+		if err := s.renderTemplateFromFile(m, template.FileName(), peekedBody); err != nil {
 			return err
 		}
 	} else {
@@ -1198,7 +1202,7 @@ func (s *Server) renderTemplate(tpl string, source string) (string, error) {
 	if err := t.Execute(limitWriter, data); err != nil {
 		return "", errHTTPBadRequestTemplateExecuteFailed.Wrap("%s", err.Error())
 	}
-	return strings.TrimSpace(buf.String()), nil
+	return strings.TrimSpace(strings.ReplaceAll(buf.String(), "\\n", "\n")), nil // replace any remaining "\n" (those outside of template curly braces) with newlines
 }
 
 func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser) error {

+ 55 - 0
server/server_test.go

@@ -3069,6 +3069,61 @@ func TestServer_MessageTemplate_UnsafeSprigFunctions(t *testing.T) {
 	require.Equal(t, 40043, toHTTPError(t, response.Body.String()).Code)
 }
 
+func TestServer_MessageTemplate_InlineNewlines(t *testing.T) {
+	t.Parallel()
+	s := newTestServer(t, newTestConfig(t))
+	response := request(t, s, "PUT", "/mytopic", `{}`, map[string]string{
+		"X-Message":  `{{"New\nlines"}}`,
+		"X-Title":    `{{"New\nlines"}}`,
+		"X-Template": "1",
+	})
+
+	require.Equal(t, 200, response.Code)
+	m := toMessage(t, response.Body.String())
+	require.Equal(t, `New
+lines`, m.Message)
+	require.Equal(t, `New
+lines`, m.Title)
+}
+
+func TestServer_MessageTemplate_InlineNewlinesOutsideOfTemplate(t *testing.T) {
+	t.Parallel()
+	s := newTestServer(t, newTestConfig(t))
+	response := request(t, s, "PUT", "/mytopic", `{"foo":"bar","food":"bag"}`, map[string]string{
+		"X-Message":  `{{.foo}}{{"\n"}}{{.food}}`,
+		"X-Title":    `{{.food}}{{"\n"}}{{.foo}}`,
+		"X-Template": "1",
+	})
+
+	require.Equal(t, 200, response.Code)
+	m := toMessage(t, response.Body.String())
+	require.Equal(t, `bar
+bag`, m.Message)
+	require.Equal(t, `bag
+bar`, m.Title)
+}
+
+func TestServer_MessageTemplate_TemplateFileNewlines(t *testing.T) {
+	t.Parallel()
+	c := newTestConfig(t)
+	c.TemplateDir = t.TempDir()
+	require.NoError(t, os.WriteFile(filepath.Join(c.TemplateDir, "newline.yml"), []byte(`
+title: |
+  {{.food}}{{"\n"}}{{.foo}}
+message: |
+  {{.foo}}{{"\n"}}{{.food}}
+`), 0644))
+	s := newTestServer(t, c)
+	response := request(t, s, "POST", "/mytopic?template=newline", `{"foo":"bar","food":"bag"}`, nil)
+	fmt.Println(response.Body.String())
+	require.Equal(t, 200, response.Code)
+	m := toMessage(t, response.Body.String())
+	require.Equal(t, `bar
+bag`, m.Message)
+	require.Equal(t, `bag
+bar`, m.Title)
+}
+
 var (
 	//go:embed testdata/webhook_github_comment_created.json
 	githubCommentCreatedJSON string

+ 32 - 5
server/types.go

@@ -245,19 +245,46 @@ func (q *queryFilter) Pass(msg *message) bool {
 	return true
 }
 
+// templateMode represents the mode in which templates are used
+//
+// It can be
+// - empty: templating is disabled
+// - a boolean string (yes/1/true/no/0/false): inline-templating mode
+// - a filename (e.g. grafana): template mode with a file
 type templateMode string
 
+// Enabled returns true if templating is enabled
 func (t templateMode) Enabled() bool {
 	return t != ""
 }
 
-func (t templateMode) Name() string {
-	if isBoolValue(string(t)) {
-		return ""
-	}
-	return string(t)
+// InlineMode returns true if inline-templating mode is enabled
+func (t templateMode) InlineMode() bool {
+	return t.Enabled() && isBoolValue(string(t))
+}
+
+// FileMode returns true if file-templating mode is enabled
+func (t templateMode) FileMode() bool {
+	return t.Enabled() && !isBoolValue(string(t))
 }
 
+// FileName returns the filename if file-templating mode is enabled, or an empty string otherwise
+func (t templateMode) FileName() string {
+	if t.FileMode() {
+		return string(t)
+	}
+	return ""
+}
+
+// templateFile represents a template file with title and message
+// It is used for file-based templates, e.g. grafana, influxdb, etc.
+//
+// Example YAML:
+//
+//	  title: "Alert: {{ .Title }}"
+//	  message: |
+//		   This is a {{ .Type }} alert.
+//		   It can be multiline.
 type templateFile struct {
 	Title   *string `yaml:"title"`
 	Message *string `yaml:"message"`