Skip to content

Commit a04f2f9

Browse files
committed
Bla
1 parent 9247dac commit a04f2f9

File tree

3 files changed

+85
-42
lines changed

3 files changed

+85
-42
lines changed

docs/publish.md

+31-28
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,37 @@ Here's an example with a custom message, tags and a priority:
938938
file_get_contents('https://ntfy.sh/mywebhook/publish?message=Webhook+triggered&priority=high&tags=warning,skull');
939939
```
940940

941+
942+
## JSON templating
943+
Some services let you specify a webhook URL but do not let you modify the webhook body (e.g. GitHub, Grafana). Instead of using a separate
944+
bridge program to parse the webhook body into the format ntfy expects, you can include a templated message and/or a templated title
945+
which will be populated based on the fields of the webhook body (so long as the webhook body is valid JSON).
946+
947+
Enable templating by setting the `X-Template` header (or its aliases `Template` or `tpl`) to `yes`, or (more appropriately for webhooks)
948+
by setting the `?template=yes` query parameter. Then, include templates in your message and/or title by including paths to the
949+
appropriate JSON fields surrounded by `${` and `}`, e.g. `${alert.title}` or `${error.desc}`, depending on your JSON payload.
950+
951+
Please refer to the [GJSON docs](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) for supported JSON path syntax, as well as
952+
[gjson.dev](https://gjson.dev/) to test your templates.
953+
954+
=== "HTTP"
955+
``` http
956+
POST /mytopic HTTP/1.1
957+
Host: ntfy.sh
958+
X-Message: Error message: ${error.desc}
959+
X-Title: ${hostname}: A ${error.level} error has occurred
960+
X-Template: yes
961+
962+
{"hostname": "philipp-pc", "error": {"level": "severe", "desc": "Disk has run out of space"}}
963+
```
964+
965+
The example above would send a notification with a title "philipp-pc: A severe error has occurred" and a message "Error message: Disk has run out of space".
966+
967+
For Grafana webhooks, you might find it helpful to use the headers `X-Title: Grafana alert: ${title}` and `X-Message: ${message}`.
968+
Alternatively, you can include the params in the webhook URL. For example, by
969+
appending `?template=yes&title=Grafana alert: ${title}&message=${message}` to the URL.
970+
971+
941972
## Publish as JSON
942973
_Supported on:_ :material-android: :material-apple: :material-firefox:
943974

@@ -3557,34 +3588,6 @@ ntfy server plays the role of the Push Gateway, as well as the Push Provider. Un
35573588
!!! info
35583589
This is not a generic Matrix Push Gateway. It only works in combination with UnifiedPush and ntfy.
35593590

3560-
### Message and Title Templates
3561-
Some services let you specify a webhook URL but do not let you modify the webhook body (e.g., Grafana). Instead of using a separate
3562-
bridge program to parse the webhook body into the format ntfy expects, you can include a templated message and/or a templated title
3563-
which will be populated based on the fields of the webhook body (so long as the webhook body is valid JSON).
3564-
3565-
Enable templating by setting the `X-Template` header (or its aliases `Template` or `tpl`) to "yes". Then, include templates
3566-
in your message and/or title (no other fields can be filled with a template at this time) by including paths to the
3567-
appropriate JSON fields surrounded by `${` and `}`. See an example below.
3568-
See [GJSON docs](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) for supported JSON path syntax.
3569-
[https://gjson.dev/](https://gjson.dev/) is a great resource for testing your templates.
3570-
3571-
=== "HTTP"
3572-
``` http
3573-
POST /mytopic HTTP/1.1
3574-
Host: ntfy.sh
3575-
X-Message: Error message: ${error.desc}
3576-
X-Title: ${hostname}: A ${error.level} error has occurred
3577-
X-Template: yes
3578-
3579-
{"hostname": "philipp-pc", "error": {"level": "severe", "desc": "Disk has run out of space"}}
3580-
```
3581-
3582-
The example above would send a notification with a title "philipp-pc: A severe error has occurred" and a message "Error message: Disk has run out of space".
3583-
3584-
For Grafana webhooks, you might find it helpful to use the headers `X-Title: Grafana alert: ${title}` and `X-Message: ${message}`.
3585-
Alternatively, you can include the params in the webhook URL. For example, by
3586-
appending `?template=yes&title=Grafana alert: ${title}&message=${message}` to the URL.
3587-
35883591
## Public topics
35893592
Obviously all topics on ntfy.sh are public, but there are a few designated topics that are used in examples, and topics
35903593
that you can use to try out what [authentication and access control](#authentication) looks like.

server/server.go

+25-14
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ import (
2323
"strconv"
2424
"strings"
2525
"sync"
26+
"text/template"
2627
"time"
2728
"unicode/utf8"
2829

2930
"github.com/emersion/go-smtp"
3031
"github.com/gorilla/websocket"
3132
"github.com/prometheus/client_golang/prometheus/promhttp"
32-
"github.com/tidwall/gjson"
3333
"golang.org/x/sync/errgroup"
3434
"heckel.io/ntfy/v2/log"
3535
"heckel.io/ntfy/v2/user"
@@ -1095,32 +1095,43 @@ func (s *Server) handleBodyAsTextMessage(m *message, body *util.PeekedReadCloser
10951095
}
10961096

10971097
func (s *Server) handleBodyAsTemplatedTextMessage(m *message, body *util.PeekedReadCloser) error {
1098-
body, err := util.Peek(body, jsonBodyBytesLimit)
1098+
body, err := util.Peek(body, max(s.config.MessageSizeLimit, jsonBodyBytesLimit))
10991099
if err != nil {
11001100
return err
11011101
} else if body.LimitReached {
11021102
return errHTTPEntityTooLargeJSONBody
11031103
}
11041104
peekedBody := strings.TrimSpace(string(body.PeekedBytes))
1105-
if !gjson.Valid(peekedBody) {
1106-
return errHTTPBadRequestTemplatedMessageNotJSON
1107-
}
1108-
m.Message = replaceGJSONTemplate(m.Message, peekedBody)
1109-
m.Title = replaceGJSONTemplate(m.Title, peekedBody)
1105+
m.Message = replaceTemplate(m.Message, peekedBody)
1106+
m.Title = replaceTemplate(m.Title, peekedBody)
11101107
if len(m.Message) > s.config.MessageSizeLimit {
11111108
return errHTTPBadRequestTemplatedMessageTooLarge
11121109
}
11131110
return nil
11141111
}
11151112

1116-
func replaceGJSONTemplate(template string, source string) string {
1117-
matches := templateVarRegex.FindAllStringSubmatch(template, -1)
1118-
for _, m := range matches {
1119-
if result := gjson.Get(source, m[1]); result.Exists() {
1120-
template = strings.ReplaceAll(template, fmt.Sprintf(templateVarFormat, m[1]), result.String())
1121-
}
1113+
func replaceTemplate(tpl string, source string) string {
1114+
rendered, err := replaceTemplateInternal(tpl, source)
1115+
if err != nil {
1116+
return "<invalid template>"
1117+
}
1118+
return rendered
1119+
}
1120+
1121+
func replaceTemplateInternal(tpl string, source string) (string, error) {
1122+
var data any
1123+
if err := json.Unmarshal([]byte(source), &data); err != nil {
1124+
return "", err
1125+
}
1126+
t, err := template.New("").Parse(tpl)
1127+
if err != nil {
1128+
return "", err
1129+
}
1130+
var buf bytes.Buffer
1131+
if err := t.Execute(&buf, data); err != nil {
1132+
return "", err
11221133
}
1123-
return template
1134+
return buf.String(), nil
11241135
}
11251136

11261137
func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message, body *util.PeekedReadCloser) error {

0 commit comments

Comments
 (0)