From 7c6e510341f3965c5e5e45ecc8b6a036d57770c8 Mon Sep 17 00:00:00 2001 From: Chris Fulljames Date: Sat, 19 Jul 2025 09:20:12 -0400 Subject: [PATCH] More work on push notifications --- src/littlesongplace/__init__.py | 1 + src/littlesongplace/comments.py | 5 ++- src/littlesongplace/push_notifications.py | 39 +++++++++++++++++++---- src/littlesongplace/songs.py | 9 +++--- src/littlesongplace/static/service.js | 2 +- 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/littlesongplace/__init__.py b/src/littlesongplace/__init__.py index 8153ff1..d9dbf1b 100644 --- a/src/littlesongplace/__init__.py +++ b/src/littlesongplace/__init__.py @@ -38,6 +38,7 @@ app.register_blueprint(profiles.bp) app.register_blueprint(push_notifications.bp) app.register_blueprint(songs.bp) db.init_app(app) +push_notifications.init_app(app) if "DATA_DIR" in os.environ: # Running on server behind proxy diff --git a/src/littlesongplace/comments.py b/src/littlesongplace/comments.py index f42a63c..5ba38af 100644 --- a/src/littlesongplace/comments.py +++ b/src/littlesongplace/comments.py @@ -188,7 +188,10 @@ def comment(): # Send push notifications push_notifications.notify( - notification_targets, f"Comment from {g.username}", content) + notification_targets, + title=f"Comment from {g.username}", + body=content, + url="/activity") db.commit() diff --git a/src/littlesongplace/push_notifications.py b/src/littlesongplace/push_notifications.py index 1cd1960..6e29ff5 100644 --- a/src/littlesongplace/push_notifications.py +++ b/src/littlesongplace/push_notifications.py @@ -1,11 +1,13 @@ import json import threading import enum +from datetime import datetime, timedelta, timezone +import click import pywebpush from flask import Blueprint, current_app, g, request -from . import auth, datadir, db +from . import auth, datadir, db, songs bp = Blueprint("push-notifications", __name__, url_prefix="/push-notifications") @@ -123,19 +125,19 @@ def get_user_subscriptions(userid): current_app.logger.error(f"Invalid subscription: {s}") return subs -def notify_all(title, body, _except=None): +def notify_all(title, body, url, _except=None): # Notify all users (who have notifications enabled) rows = db.query("SELECT * FROM users") userids = [r["userid"] for r in rows] if _except in userids: userids.remove(_except) - notify(userids, title, body) + notify(userids, title, body, url) -def notify(userids, title, body): +def notify(userids, title, body, url): # Send push notifications in background thread (could take a while) thread = threading.Thread( target=_do_push, - args=(current_app._get_current_object(), userids, title, body)) + args=(current_app._get_current_object(), userids, title, body, url)) push_threads.append(thread) thread.start() @@ -144,8 +146,8 @@ def wait_all(): for thread in push_copy: thread.join() -def _do_push(app, userids, title, body): - data = {"title": title, "body": body} +def _do_push(app, userids, title, body, url): + data = {"title": title, "body": body, "url": url} data_str = json.dumps(data) private_key_path = datadir.get_vapid_private_key_path() @@ -178,3 +180,26 @@ def _do_push(app, userids, title, body): push_threads.remove(threading.current_thread()) +@click.command("notify-new-songs") +def notify_new_songs_cmd(): + """Notify subscribed users that new songs have been uploaded""" + with current_app.app_context(): + one_day = timedelta(days=1) + yesterday = (datetime.now(timezone.utc) - one_day).isoformat() + new_songs = songs.get_uploaded_since(yesterday) + unique_users = sorted(set(s.username for s in new_songs)) + + title = None + _except = None + if len(new_songs) == 1: + title = f"New song from {unique_users[0]}" + _except = new_songs[0].userid + elif len(new_songs) > 1: + title = f"New songs from {', '.join(unique_users)}" + + if title: + print(title) + notify_all(title, body=None, url="/", _except=_except) + +def init_app(app): + app.cli.add_command(notify_new_songs_cmd) diff --git a/src/littlesongplace/songs.py b/src/littlesongplace/songs.py index 89ee4a7..a2ed3f6 100644 --- a/src/littlesongplace/songs.py +++ b/src/littlesongplace/songs.py @@ -14,7 +14,7 @@ from flask import Blueprint, current_app, g, render_template, request, redirect, from yt_dlp import YoutubeDL from yt_dlp.utils import DownloadError -from . import auth, comments, colors, datadir, db, push_notifications, users +from . import auth, comments, colors, datadir, db, users from .sanitize import sanitize_user_text from .logutils import flash_and_log @@ -132,6 +132,9 @@ def get_for_playlist(playlistid): def get_for_event(eventid): return _from_db("SELECT * FROM songs_view WHERE eventid = ?", [eventid]) +def get_uploaded_since(timestamp): + return _from_db("SELECT * FROM songs_view WHERE created > ?", [timestamp]) + def _from_db(query, args=()): songs_data = db.query(query, args) songs = [] @@ -401,10 +404,6 @@ def create_song(): flash_and_log(f"Successfully uploaded '{title}'", "success") - # Send push notifications to all other users - push_notifications.notify_all( - f"New song from {g.username}", title, _except=g.userid) - return False # No error def convert_song(tmp_file, request_file, yt_url): diff --git a/src/littlesongplace/static/service.js b/src/littlesongplace/static/service.js index f1e53e2..8f7ee6e 100644 --- a/src/littlesongplace/static/service.js +++ b/src/littlesongplace/static/service.js @@ -18,7 +18,7 @@ self.addEventListener("pushsubscriptionchanged", (event) => { headers: {"Content-Type": "application/json"}, body: JSON.stringify(subscription) }); - }); + }) ); }); -- 2.39.5