feat: add ntfy push notifications for new requests

- Add ntfy server/topic settings in /admin/settings next to Hermes API key
- POST a push notification to the configured topic on every new customer request
- Add a Send Test ntfy button to verify configuration
- Add requests dependency for ntfy HTTP calls
- Bump version to 0.5.6
This commit is contained in:
Troll (Hermes Agent) 2026-08-07 17:14:02 +00:00
parent 410a9cf021
commit a56f6e498b
6 changed files with 105 additions and 8 deletions

68
app.py
View file

@ -64,6 +64,9 @@ from models import SCHEMA, get_requests_by_email, log_revision, list_revision_hi
from mutagen.mp3 import MP3
from mutagen.easyid3 import EasyID3
# HTTP client for ntfy push notifications
import requests
# ---------------------------------------------------------------------------
# Genre / decade helpers
@ -411,6 +414,36 @@ def mask_api_key(key):
return '*' * (len(key) - 6) + key[-6:]
def get_ntfy_config():
"""Return the effective ntfy server URL and topic from runtime settings."""
cfg = load_booth_settings()
return {
'server': cfg.get('ntfy_server', ''),
'topic': cfg.get('ntfy_topic', ''),
}
def send_ntfy(message, title='Theme Song Booth', priority='default', tags='bell'):
"""Send a push notification to the configured ntfy topic, if configured."""
ntfy = get_ntfy_config()
server = ntfy.get('server', '').rstrip('/')
topic = ntfy.get('topic', '').strip()
if not server or not topic:
return False
url = f"{server}/{topic}"
headers = {
'Title': title,
'Priority': priority,
'Tags': tags,
}
try:
resp = requests.post(url, data=message.encode('utf-8'), headers=headers, timeout=10)
return resp.status_code in (200, 202)
except Exception:
return False
def sign_prompt_callback(rid, expires_at=None):
"""
Create a signed callback token for a specific request ID.
@ -559,6 +592,18 @@ def request_form():
return render_template('request.html', form=form_data, decades=DECADES, genres=MUSIC_GENRES), 400
rid = create_request(**form_data)
# Notify operator via ntfy when a new request comes in.
try:
send_ntfy(
f"New request #{rid} from {form_data['name']} ({form_data['email']})\nStyle: {form_data['style_genre'] or '-'}",
title='New Theme Song Request',
priority='high',
tags='musical_note'
)
except Exception:
# Push notification failure should never break the customer form.
pass
# Send confirmation email with a summary of what the customer asked for.
req = get_request_by_id(rid)
if req:
@ -1626,6 +1671,28 @@ def admin_settings():
flash('Hermes API key is managed via the HERMES_API_KEY environment variable.', 'info')
return redirect(url_for('admin_settings'))
elif action == 'save_ntfy':
# Update ntfy push notification server/topic from the settings form.
cfg = load_booth_settings()
cfg['ntfy_server'] = request.form.get('ntfy_server', '').strip().rstrip('/')
cfg['ntfy_topic'] = request.form.get('ntfy_topic', '').strip()
save_booth_settings(cfg)
flash('ntfy notification settings saved.', 'success')
return redirect(url_for('admin_settings'))
elif action == 'send_test_ntfy':
# Send a test push notification to the configured ntfy topic.
ntfy = get_ntfy_config()
if not ntfy['server'] or not ntfy['topic']:
flash('Configure ntfy server and topic first.', 'error')
return redirect(url_for('admin_settings'))
ok = send_ntfy('This is a test push notification from the Trollgorithm Theme Song Booth.', title='Test Notification', priority='high', tags='test_tube')
if ok:
flash('Test ntfy notification sent.', 'success')
else:
flash('Failed to send ntfy test notification. Check server and topic.', 'error')
return redirect(url_for('admin_settings'))
return render_template(
'admin/settings.html',
health=health,
@ -1653,6 +1720,7 @@ def admin_settings():
hermes_key_set=hermes_key_set,
hermes_key_just_generated=hermes_key_just_generated,
version=current_app.config['VERSION'],
ntfy=runtime_settings,
)