Skip to content

Commit 913b59b

Browse files
Merge pull request #1064 from binwiederhier/templating-3
Message templating
2 parents f971377 + 4692ca7 commit 913b59b

13 files changed

+521
-122
lines changed

docs/publish.md

+138
Large diffs are not rendered by default.

docs/releases.md

+6
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,12 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
13381338

13391339
## Not released yet
13401340

1341+
### ntfy server v2.9.1 (UNRELEASED)
1342+
1343+
**Features:**
1344+
1345+
* [Message templating](publish.md#message-templating): You can now include a message and/or title template that will be filled with values from a JSON body (e.g. `curl -gd '{"alert":"Disk space low"}' "ntfy.sh/mytopic?tpl=1&m={{.alert}}"`), which is great for services that let you specify a webhook URL but do not let you change the webhook body (such as GitHub, or Grafana). ([#724](https://github.com/binwiederhier/ntfy/issues/724), thanks to [@wunter8](https://github.com/wunter8) for implementing)
1346+
13411347
### ntfy Android app v1.16.1 (UNRELEASED)
13421348

13431349
**Features:**
Loading

docs/static/js/extra.js

+66-62
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,103 @@
11
// Link tabs, as per https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/#linked-tabs
22

3-
const savedCodeTab = localStorage.getItem('savedTab')
4-
const codeTabs = document.querySelectorAll(".tabbed-set > input")
3+
const savedCodeTab = localStorage.getItem("savedTab");
4+
const codeTabs = document.querySelectorAll(".tabbed-set > input");
55
for (const tab of codeTabs) {
6-
tab.addEventListener("click", () => {
7-
const current = document.querySelector(`label[for=${tab.id}]`)
8-
const pos = current.getBoundingClientRect().top
9-
const labelContent = current.innerHTML
10-
const labels = document.querySelectorAll('.tabbed-set > label, .tabbed-alternate > .tabbed-labels > label')
11-
for (const label of labels) {
12-
if (label.innerHTML === labelContent) {
13-
document.querySelector(`input[id=${label.getAttribute('for')}]`).checked = true
14-
}
15-
}
16-
17-
// Preserve scroll position
18-
const delta = (current.getBoundingClientRect().top) - pos
19-
window.scrollBy(0, delta)
6+
tab.addEventListener("click", () => {
7+
const current = document.querySelector(`label[for=${tab.id}]`);
8+
const pos = current.getBoundingClientRect().top;
9+
const labelContent = current.innerHTML;
10+
const labels = document.querySelectorAll(".tabbed-set > label, .tabbed-alternate > .tabbed-labels > label");
11+
for (const label of labels) {
12+
if (label.innerHTML === labelContent) {
13+
document.querySelector(`input[id=${label.getAttribute("for")}]`).checked = true;
14+
}
15+
}
16+
17+
// Preserve scroll position
18+
const delta = (current.getBoundingClientRect().top) - pos;
19+
window.scrollBy(0, delta);
2020

21-
// Save
22-
localStorage.setItem('savedTab', labelContent)
23-
})
21+
// Save
22+
localStorage.setItem("savedTab", labelContent);
23+
});
2424

25-
// Select saved tab
26-
const current = document.querySelector(`label[for=${tab.id}]`)
27-
const labelContent = current.innerHTML
28-
if (savedCodeTab === labelContent) {
29-
tab.checked = true
30-
}
25+
// Select saved tab
26+
const current = document.querySelector(`label[for=${tab.id}]`);
27+
const labelContent = current.innerHTML;
28+
if (savedCodeTab === labelContent) {
29+
tab.checked = true;
30+
}
3131
}
3232

3333
// Lightbox for screenshot
3434

35-
const lightbox = document.createElement('div');
36-
lightbox.classList.add('lightbox');
35+
const lightbox = document.createElement("div");
36+
lightbox.classList.add("lightbox");
3737
document.body.appendChild(lightbox);
3838

3939
const showScreenshotOverlay = (e, el, group, index) => {
40-
lightbox.classList.add('show');
41-
document.addEventListener('keydown', nextScreenshotKeyboardListener);
42-
return showScreenshot(e, group, index);
40+
lightbox.classList.add("show");
41+
document.addEventListener("keydown", nextScreenshotKeyboardListener);
42+
return showScreenshot(e, group, index);
4343
};
4444

4545
const showScreenshot = (e, group, index) => {
46-
const actualIndex = resolveScreenshotIndex(group, index);
47-
lightbox.innerHTML = '<div class="close-lightbox"></div>' + screenshots[group][actualIndex].innerHTML;
48-
lightbox.querySelector('img').onclick = (e) => { return showScreenshot(e, group, actualIndex+1); };
49-
currentScreenshotGroup = group;
50-
currentScreenshotIndex = actualIndex;
51-
e.stopPropagation();
52-
return false;
46+
const actualIndex = resolveScreenshotIndex(group, index);
47+
lightbox.innerHTML = "<div class=\"close-lightbox\"></div>" + screenshots[group][actualIndex].innerHTML;
48+
lightbox.querySelector("img").onclick = (e) => {
49+
return showScreenshot(e, group, actualIndex + 1);
50+
};
51+
currentScreenshotGroup = group;
52+
currentScreenshotIndex = actualIndex;
53+
e.stopPropagation();
54+
return false;
5355
};
5456

5557
const nextScreenshot = (e) => {
56-
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex+1);
58+
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex + 1);
5759
};
5860

5961
const previousScreenshot = (e) => {
60-
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex-1);
62+
return showScreenshot(e, currentScreenshotGroup, currentScreenshotIndex - 1);
6163
};
6264

6365
const resolveScreenshotIndex = (group, index) => {
64-
if (index < 0) {
65-
return screenshots[group].length - 1;
66-
} else if (index > screenshots[group].length - 1) {
67-
return 0;
68-
}
69-
return index;
66+
if (index < 0) {
67+
return screenshots[group].length - 1;
68+
} else if (index > screenshots[group].length - 1) {
69+
return 0;
70+
}
71+
return index;
7072
};
7173

7274
const hideScreenshotOverlay = (e) => {
73-
lightbox.classList.remove('show');
74-
document.removeEventListener('keydown', nextScreenshotKeyboardListener);
75+
lightbox.classList.remove("show");
76+
document.removeEventListener("keydown", nextScreenshotKeyboardListener);
7577
};
7678

7779
const nextScreenshotKeyboardListener = (e) => {
78-
switch (e.keyCode) {
79-
case 37:
80-
previousScreenshot(e);
81-
break;
82-
case 39:
83-
nextScreenshot(e);
84-
break;
85-
}
80+
switch (e.keyCode) {
81+
case 37:
82+
previousScreenshot(e);
83+
break;
84+
case 39:
85+
nextScreenshot(e);
86+
break;
87+
}
8688
};
8789

88-
let currentScreenshotGroup = '';
90+
let currentScreenshotGroup = "";
8991
let currentScreenshotIndex = 0;
9092
let screenshots = {};
91-
Array.from(document.getElementsByClassName('screenshots')).forEach((sg) => {
92-
const group = sg.id;
93-
screenshots[group] = [...sg.querySelectorAll('a')];
94-
screenshots[group].forEach((el, index) => {
95-
el.onclick = (e) => { return showScreenshotOverlay(e, el, group, index); };
96-
});
93+
Array.from(document.getElementsByClassName("screenshots")).forEach((sg) => {
94+
const group = sg.id;
95+
screenshots[group] = [...sg.querySelectorAll("a")];
96+
screenshots[group].forEach((el, index) => {
97+
el.onclick = (e) => {
98+
return showScreenshotOverlay(e, el, group, index);
99+
};
100+
});
97101
});
98102

99103
lightbox.onclick = hideScreenshotOverlay;

go.sum

-22
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
11
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
22
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
33
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
4-
cloud.google.com/go/compute v1.25.0 h1:H1/4SqSUhjPFE7L5ddzHOfY2bCAvjwNRZPNl6Ni5oYU=
5-
cloud.google.com/go/compute v1.25.0/go.mod h1:GR7F0ZPZH8EhChlMo9FkLd7eUTwEymjqQagxzilIxIE=
64
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
75
cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls=
86
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
97
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
108
cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
119
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
12-
cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc=
13-
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
1410
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
1511
cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
16-
cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg=
17-
cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
1812
cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXmjuEaE=
1913
cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA=
20-
cloud.google.com/go/storage v1.39.0 h1:brbjUa4hbDHhpQf48tjqMaXEV+f1OGoaTmQau9tmCsA=
21-
cloud.google.com/go/storage v1.39.0/go.mod h1:OAEj/WZwUYjA3YHQ10/YcN9ttGuEpLwvaoyBXIPikEk=
2214
cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY=
2315
cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o=
2416
firebase.google.com/go/v4 v4.13.0 h1:meFz9nvDNh/FDyrEykoAzSfComcQbmnQSjoHrePRqeI=
@@ -41,8 +33,6 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
4133
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
4234
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
4335
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
44-
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
45-
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
4636
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
4737
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
4838
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -108,8 +98,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
10898
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
10999
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
110100
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
111-
github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUhuHF+DA=
112-
github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc=
113101
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
114102
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
115103
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
@@ -157,8 +145,6 @@ github.com/stripe/stripe-go/v74 v74.30.0 h1:0Kf0KkeFnY7iRhOwvTerX0Ia1BRw+eV1CVJ5
157145
github.com/stripe/stripe-go/v74 v74.30.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
158146
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
159147
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
160-
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
161-
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
162148
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
163149
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
164150
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -254,8 +240,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
254240
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
255241
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
256242
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
257-
google.golang.org/api v0.168.0 h1:MBRe+Ki4mMN93jhDDbpuRLjRddooArz4FeSObvUMmjY=
258-
google.golang.org/api v0.168.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg=
259243
google.golang.org/api v0.170.0 h1:zMaruDePM88zxZBG+NG8+reALO2rfLhe/JShitLyT48=
260244
google.golang.org/api v0.170.0/go.mod h1:/xql9M2btF85xac/VAm4PsLMTLVGUOpq4BE9R8jyNy8=
261245
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@@ -267,16 +251,10 @@ google.golang.org/appengine/v2 v2.0.5/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7
267251
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
268252
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
269253
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
270-
google.golang.org/genproto v0.0.0-20240304212257-790db918fca8 h1:Fe8QycXyEd9mJgnwB9kmw00WgB43eQ/xYO5C6gceybQ=
271-
google.golang.org/genproto v0.0.0-20240304212257-790db918fca8/go.mod h1:yA7a1bW1kwl459Ol0m0lV4hLTfrL/7Bkk4Mj2Ir1mWI=
272254
google.golang.org/genproto v0.0.0-20240318140521-94a12d6c2237 h1:PgNlNSx2Nq2/j4juYzQBG0/Zdr+WP4z5N01Vk4VYBCY=
273255
google.golang.org/genproto v0.0.0-20240318140521-94a12d6c2237/go.mod h1:9sVD8c25Af3p0rGs7S7LLsxWKFiJt/65LdSyqXBkX/Y=
274-
google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8 h1:8eadJkXbwDEMNwcB5O0s5Y5eCfyuCLdvaiOIaGTrWmQ=
275-
google.golang.org/genproto/googleapis/api v0.0.0-20240304212257-790db918fca8/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
276256
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
277257
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
278-
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 h1:IR+hp6ypxjH24bkMfEJ0yHR21+gwPWdV+/IBrPQyn3k=
279-
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
280258
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
281259
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
282260
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

server/errors.go

+4
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ var (
117117
errHTTPBadRequestWebPushSubscriptionInvalid = &errHTTP{40038, http.StatusBadRequest, "invalid request: web push payload malformed", "", nil}
118118
errHTTPBadRequestWebPushEndpointUnknown = &errHTTP{40039, http.StatusBadRequest, "invalid request: web push endpoint unknown", "", nil}
119119
errHTTPBadRequestWebPushTopicCountTooHigh = &errHTTP{40040, http.StatusBadRequest, "invalid request: too many web push topic subscriptions", "", nil}
120+
errHTTPBadRequestTemplatedMessageTooLarge = &errHTTP{40041, http.StatusBadRequest, "invalid request: message or title is too large after replacing template", "", nil}
121+
errHTTPBadRequestTemplatedMessageNotJSON = &errHTTP{40042, http.StatusBadRequest, "invalid request: message body must be JSON if templating is enabled", "", nil}
122+
errHTTPBadRequestTemplateInvalid = &errHTTP{40043, http.StatusBadRequest, "invalid request: could not parse template", "", nil}
123+
errHTTPBadRequestTemplateExecutionFailed = &errHTTP{40044, http.StatusBadRequest, "invalid request: template execution failed", "", nil}
120124
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}
121125
errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil}
122126
errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil}

0 commit comments

Comments
 (0)