Skip to content

fix: format notification messages for ntfy, Slack, Discord, Gotify#42

Open
bin101 wants to merge 4 commits into
feat/notificationsfrom
fix/notify-formatting
Open

fix: format notification messages for ntfy, Slack, Discord, Gotify#42
bin101 wants to merge 4 commits into
feat/notificationsfrom
fix/notify-formatting

Conversation

@bin101

@bin101 bin101 commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Problem

The previous implementation sent a raw JSON blob to the webhook URL regardless of the target system. On ntfy this shows up as unformatted JSON text with no title, no emoji, and no priority.

Solution

Auto-detect the notification system from the URL and format the payload accordingly.

System Detection Format
ntfy ntfy. in hostname {title, message, priority 1-5, tags} JSON — ntfy reads these natively
Slack hooks.slack.com {"text": "*Title*\nMessage"} Incoming Webhook
Discord discord.com/api/webhooks {"embeds": [{title, description, color}]} — indigo for runs, red for auth errors
Gotify /message path {title, message, priority 1-10}
Generic fallback {title, message} + structured fields

Example — ntfy now receives:

{
  "title": "Kudosy — Dry-Run abgeschlossen",
  "message": "🔍 97 Aktivitäten geprüft · 0 Kudos simuliert",
  "priority": 2,
  "tags": ["mag"]
}

Test plan

  • 28 unit tests pass (ruff, mypy, pytest all green)
  • Point webhook URL at ntfy instance → notification arrives with title, emoji icon, and correct priority
  • Auth error test: set invalid cookie → ntfy shows priority-4 warning notification

🤖 Generated with Claude Code

bin101 and others added 4 commits July 1, 2026 18:14
Replace the raw JSON dump with human-readable messages and auto-detect
the target system from the webhook URL.

- detect_system(url) — infers "ntfy" | "slack" | "discord" | "gotify"
  | "generic" from URL patterns
- Per-system formatters:
  • ntfy: {title, message, priority 1-5, tags [emoji aliases]}
    Posted as JSON to https://ntfy.sh/<topic> — ntfy reads these fields
    natively when Content-Type is application/json
  • Slack: {"text": "*Title*\nMessage"} (Incoming Webhook format)
  • Discord: {"embeds": [{title, description, color}]} — indigo for
    runs, red for auth errors
  • Gotify: {title, message, priority 1-10} (ntfy 1-5 × 2)
  • Generic: {title, message} + structured fields (event, given, …)
- build_run_payload() now returns human-readable title + one-line
  summary ("97 Aktivitäten geprüft · 0 Kudos simuliert"), icon prefix,
  ntfy tags, and priority (2 dry-run / 3 live / 4 error)
- build_auth_error_payload() now has title + ⚠️ message + priority 4
- 28 unit tests (6 new detection + 10 formatting + 12 carried over)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
User can now choose the notification system (ntfy / Slack / Discord /
Gotify / Generic) from a dropdown in the Settings UI instead of relying
on URL-based heuristics. `notifySystem` is persisted in settings.json
and passed directly to `send_notification()`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JSON publishing breaks when reverse proxies (nginx, Traefik, Caddy)
strip or modify the Content-Type header — ntfy then treats the body
as plain text and shows the raw JSON blob as the notification.

Switching to the headers API (plain text body + X-Title / X-Priority /
X-Tags HTTP headers) is proxy-agnostic and works with all ntfy versions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HTTP headers must be ASCII. Titles like "Kudosy — Dry-Run abgeschlossen"
contain non-ASCII characters (em dash U+2014) that caused httpx to raise
an 'ascii codec' error before the request was even sent.

ntfy URL-decodes header values, so quote() is the correct solution —
the title arrives intact in the notification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant