From 279cba5115021c9995d5fecbec5785119c967f84 Mon Sep 17 00:00:00 2001 From: nmemmert <67961292+nmemmert@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:31:44 -0400 Subject: [PATCH] Fix ntfy: use JSON body instead of headers to support emoji titles Sending emoji in HTTP headers is not valid ASCII and causes errors. Switching to ntfy's JSON body API (POST to /topic with {topic, title, message, priority, tags}) allows full UTF-8 including emoji. Co-Authored-By: Claude Sonnet 4.6 --- server/index.js | 12 ++++++------ server/notificationService.js | 36 +++++++++++++++++------------------ 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/server/index.js b/server/index.js index 2974895..59a9927 100644 --- a/server/index.js +++ b/server/index.js @@ -1566,12 +1566,12 @@ app.post('/api/test-ntfy', async (req, res) => { if (!ntfy_topic) return res.status(400).json({ error: 'ntfy topic is required' }); const base = (ntfy_url || 'https://ntfy.sh').replace(/\/$/, ''); try { - await axios.post(`${base}/${ntfy_topic}`, 'This is a test notification from SkyWatch', { - headers: { - Title: encodeURIComponent('SkyWatch Test'), - Priority: 'default', - Tags: 'white_check_mark', - }, + await axios.post(`${base}/`, { + topic: ntfy_topic, + title: '🔔 SkyWatch Test', + message: 'This is a test notification from SkyWatch', + priority: 3, + tags: ['white_check_mark'], }); res.json({ message: 'Test notification sent to ntfy!' }); } catch (error) { diff --git a/server/notificationService.js b/server/notificationService.js index 1a6ece7..c265a3e 100644 --- a/server/notificationService.js +++ b/server/notificationService.js @@ -179,15 +179,14 @@ class NotificationService { const ntfyTopic = this.config.ntfy_topic; if (ntfyTopic) { try { - const groupedBody = pendingAlerts.map(({ resource, incident }) => - `${incident.type === 'started' ? '[DOWN]' : '[UP]'} ${resource.name}` - ).join('\n'); - await axios.post(`${ntfyUrl.replace(/\/$/, '')}/${ntfyTopic}`, groupedBody, { - headers: { - Title: encodeURIComponent(`${pendingAlerts.length} monitors changed status`), - Priority: 'high', - Tags: 'warning', - }, + await axios.post(`${ntfyUrl.replace(/\/$/, '')}/`, { + topic: ntfyTopic, + title: `⚠️ ${pendingAlerts.length} monitors changed status`, + message: pendingAlerts.map(({ resource, incident }) => + `${incident.type === 'started' ? '🔴' : '🟢'} ${resource.name}` + ).join('\n'), + priority: 4, + tags: ['warning'], }); } catch (_) {} } @@ -454,18 +453,17 @@ class NotificationService { const isDown = type === 'started'; const isSlow = type === 'slow'; - const titleText = isDown ? `${resource.name} is DOWN` : isSlow ? `${resource.name} is SLOW` : `${resource.name} is UP`; - const priority = isDown ? 'high' : isSlow ? 'default' : 'low'; - const tags = isDown ? 'red_circle,warning' : isSlow ? 'yellow_circle' : 'green_circle,white_check_mark'; - const body = resource.url || resource.name; + const title = isDown ? `🔴 ${resource.name} is DOWN` : isSlow ? `🟡 ${resource.name} is SLOW` : `🟢 ${resource.name} is UP`; + const priority = isDown ? 4 : isSlow ? 3 : 2; + const tags = isDown ? ['red_circle', 'warning'] : isSlow ? ['yellow_circle'] : ['green_circle', 'white_check_mark']; try { - await axios.post(`${ntfyUrl.replace(/\/$/, '')}/${ntfyTopic}`, body, { - headers: { - Title: encodeURIComponent(titleText), - Priority: priority, - Tags: tags, - }, + await axios.post(`${ntfyUrl.replace(/\/$/, '')}/`, { + topic: ntfyTopic, + title, + message: resource.url || resource.name, + priority, + tags, }); } catch (error) { // ntfy error handled silently