Send notifications N hours after arrival, polling once per hour

This commit is contained in:
Oskar Winkels 2021-06-10 23:14:15 +02:00
parent b8e704c300
commit 618f00a09a
Signed by: o.winkels
GPG Key ID: E7484A06E99DAEF1
4 changed files with 118 additions and 28 deletions

View File

@ -30,3 +30,5 @@ json_indent = 4
# sender info: usually mailto link to responsible party to contact about issues
push_private_key = abcdefghijklm_NOPQRSTUVWXYZ-0123456789
push_sender_info = mailto:it@fasttube.de
# when to notify users, in hours after arrival
notify_after_hrs = 10

View File

@ -30,3 +30,6 @@ class Config:
def __getitem__(self, key):
return self.config['global'].get(key)
def __repr__(self):
return repr(self.config.items('global'))

View File

@ -22,14 +22,19 @@ from flask import Flask, request, redirect
app = Flask(__name__, static_folder='../web')
from pywebpush import webpush, WebPushException
pushsubs = db.table('pushsubs')
if config['delete_after_days']:
from .deleter import Deleter
deleter = Deleter(db, int(config['delete_after_days']))
if config['notify_after_hrs']:
from .notifier import Notifier
notifier = Notifier(db, int(config['notify_after_hrs']), {
'private_key': config['push_private_key'],
'claims': {'sub': config['push_sender_info']}
})
def shutdown():
print('\rReceived stop signal, stopping threads...')
deleter.stop()
@ -37,6 +42,7 @@ def shutdown():
atexit.register(shutdown)
@app.route('/guidelines')
def get_guidelines():
dest = config['guideline_url'] or None
@ -186,32 +192,25 @@ def post_pushsub():
@app.route('/testpush/<name>')
def testpush(name):
if not 'Authorization' in request.headers:
return 'Error: No Authorization', 401, {'WWW-Authenticate': 'Basic'}
if request.authorization.username != config['admin_user']:
return "Wrong username", 403
if request.authorization.password != config['admin_pass']:
return "Wrong password", 403
Entry = Query()
ps = pushsubs.search(Entry.name == name)[0]
arrivals = db.search((Entry.name == name) & (Entry.departure == None))
arr = db.search((Entry.name == name) & (Entry.departure == None))
arr = arr if len(arr) else None
if len(arrivals) == 0:
print("User is not logged in :(")
return "Error: User is not logged in :(", 409
print(ps)
error = notifier.notify_user(arrivals[0])
subscription = ps['sub']
notification = {
'title': "Forgot to sign out?",
'body': "You didn't sign out of ftracker yet",
'arr': arr
}
if error:
return 'Error: ' + error, 201
try:
webpush(
subscription,
json.dumps(notification, indent=SPACES),
vapid_private_key = config['push_private_key'],
vapid_claims = {
'sub': config['push_sender_info']
},
verbose=True
)
return 'OK', 201
except WebPushException as exc:
print(exc)
return 'Error', 500
return 'OK', 201

86
ftracker/notifier.py Normal file
View File

@ -0,0 +1,86 @@
import json
from threading import Thread, Event
from datetime import datetime, timedelta
from tinydb import Query
from pywebpush import webpush, WebPushException
ONE_HOUR_IN_S = 60 * 60
class Notifier(Thread):
def notify_user(self, arrival):
pushsubs = self.db.table('pushsubs')
Entry = Query()
ps = pushsubs.search(Entry.name == arrival['name'])
if len(ps) == 0:
print("User is not subscribed to notifications :(")
return "User is not subscribed to notifications :("
ps = ps[0]
print("Sending notification", arrival, ps)
subscription = ps['sub']
notification = {
'title': "Forgot to sign out?",
'body': "You didn't sign out of ftracker yet",
'arr': arrival
}
try:
webpush(
subscription,
json.dumps(notification),
vapid_private_key = self.vapid_creds['private_key'],
vapid_claims = self.vapid_creds['claims']
)
print("Notification sent")
return None
except WebPushException as exc:
print("Notification failed", exc)
return repr(exc)
def notify_logged_in_users(self):
print("Notifying users that aren't signed out yet...")
td = timedelta(hours=self.hours)
threshold = datetime.utcnow() - td
iso = threshold.isoformat() + 'Z'
Entry = Query()
arrivals = self.db.search(
(Entry.arrival < iso) & (Entry.departure == None)
)
for arrival in arrivals:
self.notify_user(arrival)
print("Notified everything until UTC", iso)
def __init__(self, db, hours, vapid_creds):
self.db = db
self.hours = hours
self.vapid_creds = vapid_creds
self.notify_logged_in_users()
Thread.__init__(self, daemon=True)
self.stopped = Event()
self.start()
def run(self):
while not self.stopped.wait(ONE_HOUR_IN_S):
self.notify_logged_in_users()
def stop(self):
self.stopped.set()