From 69f7cad809c9bc3813537f5f34264e2139f239c8 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Fri, 25 Sep 2026 09:18:10 -0700 Subject: [PATCH] Tidy ntfy notifications with markdown and a run button Hide zero counts on one compact line, list changes as bullets, add a pass/fail emoji tag, and replace the tap-to-open run link with an "Open run" action button. Co-Authored-By: Claude Opus 5.5 --- notify.js | 49 +++++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/notify.js b/notify.js index 63cb582..7ea2f98 100644 --- a/notify.js +++ b/notify.js @@ -1,51 +1,56 @@ const { readFileSync } = require('node:fs'); const { NTFY_TOPIC_URL, RUN_URL, STEP_OUTCOME, TEST_NOTIFICATION, WORKFLOW_KIND } = process.env; +// [noun, key]: shown as "**n** noun(s)" and hidden when zero const labels = { mirror: ['Mirror sync', 'mirror-facts.json', [ - ['Organizations checked', 'orgsChecked'], - ['GitHub repositories found', 'reposFound'], - ['Organizations created', 'orgsCreated'], - ['Org visibility changes', 'orgVisibilityChanged'], - ['Org avatars updated', 'avatarsUpdated'], - ['New mirrors', 'mirrorsCreated'], - ['Repo visibility changes', 'repoVisibilityChanged'], - ['Failures', 'failures'] + ['org', 'orgsChecked'], + ['repo', 'reposFound'], + ['created org', 'orgsCreated'], + ['org visibility change', 'orgVisibilityChanged'], + ['new mirror', 'mirrorsCreated'], + ['repo visibility change', 'repoVisibilityChanged'], + ['failure', 'failures'] ]], rename: ['Missing-mirror check', 'rename-facts.json', [ - ['Gitea organizations checked', 'orgsChecked'], - ['GitHub mirrors found', 'mirrorsFound'], - ['Renamed mirrors', 'renamed'], - ['Organizations skipped', 'skippedOrgs'], - ['Failures', 'failures'] + ['org', 'orgsChecked'], + ['mirror', 'mirrorsFound'], + ['renamed mirror', 'renamed'], + ['skipped org', 'skippedOrgs'], + ['failure', 'failures'] ]] }; +const md = text => String(text).replace(/[\\`*_[\]#]/g, '\\$&'); (async () => { if (!NTFY_TOPIC_URL || !labels[WORKFLOW_KIND]) throw new Error('Notification URL or workflow kind missing'); - const test = TEST_NOTIFICATION === 'true'; + const test = TEST_NOTIFICATION === 'true', ok = STEP_OUTCOME === 'success'; const [label, file, fields] = labels[WORKFLOW_KIND]; - const title = test ? 'Gitea backup notification test' : `${label}: ${STEP_OUTCOME === 'success' ? 'succeeded' : 'failed'}`; + const title = test ? 'Gitea backup notification test' : `${label}: ${ok ? 'succeeded' : 'failed'}`; const lines = test ? ['Notification-only test. No mirror or rename ran.'] : []; if (!test) { try { const facts = JSON.parse(readFileSync(file, 'utf8')); - lines.push(...fields.map(([name, key]) => `${name}: ${facts[key] ?? 0}`)); + const counts = fields.filter(([, k]) => facts[k]).map(([noun, k]) => `**${facts[k]}** ${noun}${facts[k] === 1 ? '' : 's'}`); + if (counts.length) lines.push(counts.join(' · ')); const changes = facts.changes || []; - if (changes.length) lines.push('Changes:', ...changes.slice(0, 10)); - if (changes.length > 10) lines.push(`...and ${changes.length - 10} more; see run logs.`); - if (facts.fatal) lines.push('Fatal error; see run logs.'); + if (changes.length) lines.push('', ...changes.slice(0, 10).map(c => `- ${md(c)}`)); + if (changes.length > 10) lines.push(`- *…and ${changes.length - 10} more; see run logs.*`); + if (facts.fatal) lines.push('', '**Fatal error**; see run logs.'); } catch { lines.push('No run summary available; see run logs.'); } } - if (RUN_URL) lines.push(`Run: ${RUN_URL}`); const res = await fetch(NTFY_TOPIC_URL, { method: 'POST', - headers: { Priority: '2', Title: title, ...(RUN_URL && { Click: RUN_URL }) }, - body: lines.join('\n'), + headers: { + Priority: '2', Title: title, Markdown: 'yes', + Tags: test ? 'test_tube' : ok ? 'white_check_mark' : 'x', + ...(RUN_URL && { Actions: `view, Open run, ${RUN_URL}` }) + }, + body: lines.join('\n') || 'Nothing to report.', signal: AbortSignal.timeout(30_000) }); if (!res.ok) throw new Error(`ntfy returned HTTP ${res.status}`);