Emojis in notifications; server caching
This commit is contained in:
parent
052ab7d411
commit
8616be12a2
6 changed files with 159 additions and 64 deletions
|
@ -85,7 +85,7 @@
|
|||
curl \<br/>
|
||||
-H "Title: Unauthorized access detected" \<br/>
|
||||
-H "Priority: urgent" \<br/>
|
||||
-H "Tags: warn,skull" \<br/>
|
||||
-H "Tags: warning,skull" \<br/>
|
||||
-d "Remote access to $(hostname) detected. Act right away." \<br/>
|
||||
<span class="ntfyUrl">ntfy.sh</span>/mytopic
|
||||
</code>
|
||||
|
@ -228,16 +228,22 @@
|
|||
curl -H "Title: Dogs are better than cats" -d "Oh my ..." <span class="ntfyUrl">ntfy.sh</span>/mytopic<br/>
|
||||
</code>
|
||||
|
||||
<h3 id="tags" class="anchor">Tagging messages (<tt>X-Tags</tt>, <tt>Tags</tt>, or <tt>ta</tt>)</h3>
|
||||
<h3 id="tags" class="anchor">Tags & emojis 🥳 🎉 (<tt>X-Tags</tt>, <tt>Tags</tt>, or <tt>ta</tt>)</h3>
|
||||
<p>
|
||||
You can tag messages with emojis (or other relevant strings). If a tag matches a <a href="https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json">known emoji short code</a>,
|
||||
it will be converted to an emoji. If it doesn't match, it will be listed below the notification. This is useful
|
||||
for things like warnings and such (⚠️, ️🚨, or 🚩), but also to simply tag messages otherwise (e.g. which script the
|
||||
message came from, ...).
|
||||
</p>
|
||||
<p class="smallMarginBottom">
|
||||
You can tag notifications with emojis (or other relevant strings). In the phone app, the tags will be converted
|
||||
to emojis and prepended to the message or title in the notification. You can set tags with the <tt>X-Tags</tt> header
|
||||
(or any of its aliases: <tt>Tags</tt>, or <tt>ta</tt>). Use <a href="https://github.com/vdurmont/emoji-java/blob/master/EMOJIS.md">this reference</a>
|
||||
to figure out what tags you can use to send emojis.
|
||||
You can set tags with the <tt>X-Tags</tt> header (or any of its aliases: <tt>Tags</tt>, or <tt>ta</tt>).
|
||||
Use <a href="https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json">this reference</a>
|
||||
to figure out what tags can be converted to emojis. In the example below, the tag "warning" matches the emoji ⚠️,
|
||||
the tag "ssh-login" doesn't match and will be displayed below the message.
|
||||
</p>
|
||||
<code>
|
||||
curl -H "Tags: warn,skull" -d "Unauthorized SSH access" <span class="ntfyUrl">ntfy.sh</span>/mytopic<br/>
|
||||
curl -H tags:thumbsup -d "Backup successful" <span class="ntfyUrl">ntfy.sh</span>/mytopic<br/>
|
||||
$ curl -H "Tags: warning,ssh-login" -d "Unauthorized SSH access" <span class="ntfyUrl">ntfy.sh</span>/mytopic<br/>
|
||||
{"id":"ZEIwjfHlSS",...,"tags":["warning","ssh-login"],"message":"Unauthorized SSH access"}
|
||||
</code>
|
||||
|
||||
<h2 id="examples" class="anchor">Examples</h2>
|
||||
|
@ -257,7 +263,7 @@
|
|||
rsync -a root@laptop /backups/laptop \<br/>
|
||||
&& zfs snapshot ... \<br/>
|
||||
&& curl -d "Laptop backup succeeded" <span class="ntfyUrl">ntfy.sh</span>/backups \<br/>
|
||||
|| curl -H tags:warn -H prio:high -d "Laptop backup failed" <span class="ntfyUrl">ntfy.sh</span>/backups
|
||||
|| curl -H tags:warning -H prio:high -d "Laptop backup failed" <span class="ntfyUrl">ntfy.sh</span>/backups
|
||||
</code>
|
||||
|
||||
<h3 id="example-web" class="anchor">Example: Server-sent messages in your web app</h3>
|
||||
|
@ -284,7 +290,7 @@
|
|||
<code>
|
||||
#!/bin/bash<br/>
|
||||
if [ "${PAM_TYPE}" = "open_session" ]; then<br/>
|
||||
curl -H tags:warn -d "SSH login: ${PAM_USER} from ${PAM_RHOST}" <span class="ntfyUrl">ntfy.sh</span>/alerts<br/>
|
||||
curl -H tags:warning -d "SSH login: ${PAM_USER} from ${PAM_RHOST}" <span class="ntfyUrl">ntfy.sh</span>/alerts<br/>
|
||||
fi
|
||||
</code>
|
||||
|
||||
|
@ -405,6 +411,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="lightbox" class="lightbox"></div>
|
||||
<script src="static/js/emoji.js"></script>
|
||||
<script src="static/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -92,7 +92,8 @@ var (
|
|||
exampleSource string
|
||||
|
||||
//go:embed static
|
||||
webStaticFs embed.FS
|
||||
webStaticFs embed.FS
|
||||
webStaticFsCached = &util.CachingEmbedFS{ModTime: time.Now(), FS: webStaticFs}
|
||||
|
||||
errHTTPBadRequest = &errHTTP{http.StatusBadRequest, http.StatusText(http.StatusBadRequest)}
|
||||
errHTTPNotFound = &errHTTP{http.StatusNotFound, http.StatusText(http.StatusNotFound)}
|
||||
|
@ -231,7 +232,7 @@ func (s *Server) handleExample(w http.ResponseWriter, r *http.Request) error {
|
|||
}
|
||||
|
||||
func (s *Server) handleStatic(w http.ResponseWriter, r *http.Request) error {
|
||||
http.FileServer(http.FS(webStaticFs)).ServeHTTP(w, r)
|
||||
http.FileServer(http.FS(webStaticFsCached)).ServeHTTP(w, r)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -86,9 +86,10 @@ const subscribeInternal = (topic, persist, delaySec) => {
|
|||
}
|
||||
if (Notification.permission === "granted") {
|
||||
notifySound.play();
|
||||
const title = (event.title) ? event.title : `${location.host}/${topic}`;
|
||||
const title = formatTitle(event);
|
||||
const message = formatMessage(event);
|
||||
const notification = new Notification(title, {
|
||||
body: event.message,
|
||||
body: message,
|
||||
icon: '/static/img/favicon.png'
|
||||
});
|
||||
notification.onclick = (e) => {
|
||||
|
@ -158,56 +159,28 @@ const rerenderDetailView = () => {
|
|||
const messageDiv = document.createElement('div');
|
||||
const tagsDiv = document.createElement('div');
|
||||
|
||||
// Figure out mapped emojis (and unmapped tags)
|
||||
let mappedEmojiTags = '';
|
||||
let unmappedTags = '';
|
||||
if (m.tags) {
|
||||
mappedEmojiTags = m.tags
|
||||
.filter(tag => tag in emojis)
|
||||
.map(tag => emojis[tag])
|
||||
.join("");
|
||||
unmappedTags = m.tags
|
||||
.filter(tag => !(tag in emojis))
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
// Figure out title and message
|
||||
let title = '';
|
||||
let message = m.message;
|
||||
if (m.title) {
|
||||
if (mappedEmojiTags) {
|
||||
title = `${mappedEmojiTags} ${m.title}`;
|
||||
} else {
|
||||
title = m.title;
|
||||
}
|
||||
} else {
|
||||
if (mappedEmojiTags) {
|
||||
message = `${mappedEmojiTags} ${m.message}`;
|
||||
} else {
|
||||
message = m.message;
|
||||
}
|
||||
}
|
||||
|
||||
entryDiv.classList.add('detailEntry');
|
||||
dateDiv.classList.add('detailDate');
|
||||
titleDiv.classList.add('detailTitle');
|
||||
messageDiv.classList.add('detailMessage');
|
||||
tagsDiv.classList.add('detailTags');
|
||||
|
||||
const dateStr = new Date(m.time * 1000).toLocaleString();
|
||||
if (m.priority && [1,2,4,5].includes(m.priority)) {
|
||||
dateDiv.innerHTML = `${dateStr} <img src="static/img/priority-${m.priority}.svg"/>`;
|
||||
} else {
|
||||
dateDiv.innerHTML = `${dateStr}`;
|
||||
}
|
||||
messageDiv.classList.add('detailMessage');
|
||||
messageDiv.innerText = message;
|
||||
messageDiv.innerText = formatMessage(m);
|
||||
entryDiv.appendChild(dateDiv);
|
||||
if (m.title) {
|
||||
titleDiv.classList.add('detailTitle');
|
||||
titleDiv.innerText = title;
|
||||
titleDiv.innerText = formatTitleA(m);
|
||||
entryDiv.appendChild(titleDiv);
|
||||
}
|
||||
entryDiv.appendChild(messageDiv);
|
||||
if (unmappedTags) {
|
||||
tagsDiv.classList.add('detailTags');
|
||||
tagsDiv.innerText = `Tags: ${unmappedTags}`;
|
||||
const otherTags = unmatchedTags(m.tags);
|
||||
if (otherTags.length > 0) {
|
||||
tagsDiv.innerText = `Tags: ${otherTags.join(", ")}`;
|
||||
entryDiv.appendChild(tagsDiv);
|
||||
}
|
||||
detailEventsList.appendChild(entryDiv);
|
||||
|
@ -311,10 +284,46 @@ const nextScreenshotKeyboardListener = (e) => {
|
|||
}
|
||||
};
|
||||
|
||||
const toEmoji = (tag) => {
|
||||
emojis
|
||||
const formatTitle = (m) => {
|
||||
if (m.title) {
|
||||
return formatTitleA(m);
|
||||
} else {
|
||||
return `${location.host}/${m.topic}`;
|
||||
}
|
||||
};
|
||||
|
||||
const formatTitleA = (m) => {
|
||||
const emojiList = toEmojis(m.tags);
|
||||
if (emojiList) {
|
||||
return `${emojiList.join(" ")} ${m.title}`;
|
||||
} else {
|
||||
return m.title;
|
||||
}
|
||||
};
|
||||
|
||||
const formatMessage = (m) => {
|
||||
if (m.title) {
|
||||
return m.message;
|
||||
} else {
|
||||
const emojiList = toEmojis(m.tags);
|
||||
if (emojiList) {
|
||||
return `${emojiList.join(" ")} ${m.message}`;
|
||||
} else {
|
||||
return m.message;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toEmojis = (tags) => {
|
||||
if (!tags) return [];
|
||||
else return tags.filter(tag => tag in emojis).map(tag => emojis[tag]);
|
||||
}
|
||||
|
||||
const unmatchedTags = (tags) => {
|
||||
if (!tags) return [];
|
||||
else return tags.filter(tag => !(tag in emojis));
|
||||
}
|
||||
|
||||
// From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
|
||||
async function* makeTextFileLineIterator(fileURL) {
|
||||
const utf8Decoder = new TextDecoder('utf-8');
|
||||
|
@ -417,14 +426,10 @@ document.querySelectorAll('.ntfyProtocol').forEach((el) => {
|
|||
el.innerHTML = window.location.protocol + "//";
|
||||
});
|
||||
|
||||
// Fetch emojis
|
||||
// Format emojis (see emoji.js)
|
||||
const emojis = {};
|
||||
fetch('static/js/emoji.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
data.forEach(emoji => {
|
||||
emoji.aliases.forEach(alias => {
|
||||
emojis[alias] = emoji.emoji;
|
||||
});
|
||||
});
|
||||
rawEmojis.forEach(emoji => {
|
||||
emoji.aliases.forEach(alias => {
|
||||
emojis[alias] = emoji.emoji;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
[
|
||||
// Original data source: https://github.com/github/gemoji/blob/master/db/emoji.json
|
||||
// Manually prepended "const rawEmojis = " to make it play nice with JS/HTML.
|
||||
|
||||
const rawEmojis = [
|
||||
{
|
||||
"emoji": "😀"
|
||||
, "description": "grinning face"
|
Loading…
Add table
Add a link
Reference in a new issue