Merge branch 'binwiederhier:main' into main
This commit is contained in:
@@ -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.
|
- [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.
|
- [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
|
- [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
|
## Projects + scripts
|
||||||
|
|
||||||
|
|||||||
@@ -991,7 +991,12 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
|
|||||||
} else if call != "" && !isBoolValue(call) && !phoneNumberRegex.MatchString(call) {
|
} else if call != "" && !isBoolValue(call) && !phoneNumberRegex.MatchString(call) {
|
||||||
return false, false, "", "", "", false, errHTTPBadRequestPhoneNumberInvalid
|
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 != "" {
|
if messageStr != "" {
|
||||||
m.Message = 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" {
|
if markdown || strings.ToLower(contentType) == "text/markdown" {
|
||||||
m.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!
|
unifiedpush = readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see GET too!
|
||||||
contentEncoding := readParam(r, "content-encoding")
|
contentEncoding := readParam(r, "content-encoding")
|
||||||
if unifiedpush || contentEncoding == "aes128gcm" {
|
if unifiedpush || contentEncoding == "aes128gcm" {
|
||||||
@@ -1119,8 +1123,8 @@ func (s *Server) handleBodyAsTemplatedTextMessage(m *message, template templateM
|
|||||||
return errHTTPEntityTooLargeJSONBody
|
return errHTTPEntityTooLargeJSONBody
|
||||||
}
|
}
|
||||||
peekedBody := strings.TrimSpace(string(body.PeekedBytes))
|
peekedBody := strings.TrimSpace(string(body.PeekedBytes))
|
||||||
if templateName := template.Name(); templateName != "" {
|
if template.FileMode() {
|
||||||
if err := s.renderTemplateFromFile(m, templateName, peekedBody); err != nil {
|
if err := s.renderTemplateFromFile(m, template.FileName(), peekedBody); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1198,7 +1202,7 @@ func (s *Server) renderTemplate(tpl string, source string) (string, error) {
|
|||||||
if err := t.Execute(limitWriter, data); err != nil {
|
if err := t.Execute(limitWriter, data); err != nil {
|
||||||
return "", errHTTPBadRequestTemplateExecuteFailed.Wrap("%s", err.Error())
|
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 {
|
func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser) error {
|
||||||
|
|||||||
@@ -3069,6 +3069,61 @@ func TestServer_MessageTemplate_UnsafeSprigFunctions(t *testing.T) {
|
|||||||
require.Equal(t, 40043, toHTTPError(t, response.Body.String()).Code)
|
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 (
|
var (
|
||||||
//go:embed testdata/webhook_github_comment_created.json
|
//go:embed testdata/webhook_github_comment_created.json
|
||||||
githubCommentCreatedJSON string
|
githubCommentCreatedJSON string
|
||||||
|
|||||||
@@ -245,19 +245,46 @@ func (q *queryFilter) Pass(msg *message) bool {
|
|||||||
return true
|
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
|
type templateMode string
|
||||||
|
|
||||||
|
// Enabled returns true if templating is enabled
|
||||||
func (t templateMode) Enabled() bool {
|
func (t templateMode) Enabled() bool {
|
||||||
return t != ""
|
return t != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t templateMode) Name() string {
|
// InlineMode returns true if inline-templating mode is enabled
|
||||||
if isBoolValue(string(t)) {
|
func (t templateMode) InlineMode() bool {
|
||||||
return ""
|
return t.Enabled() && isBoolValue(string(t))
|
||||||
}
|
|
||||||
return 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 {
|
type templateFile struct {
|
||||||
Title *string `yaml:"title"`
|
Title *string `yaml:"title"`
|
||||||
Message *string `yaml:"message"`
|
Message *string `yaml:"message"`
|
||||||
|
|||||||
Reference in New Issue
Block a user