kopia lustrzana https://github.com/snarfed/bridgy-fed
129 wiersze
4.0 KiB
Python
129 wiersze
4.0 KiB
Python
"""Send DM notifications of replies, quote posts, mentions from unbridged users."""
|
|
from datetime import timedelta
|
|
import logging
|
|
|
|
from flask import request
|
|
from granary import as1
|
|
from oauth_dropins.webutil import appengine_info, util
|
|
from oauth_dropins.webutil.flask_util import cloud_tasks_only
|
|
|
|
import common
|
|
import dms
|
|
from memcache import memcache, key
|
|
from models import PROTOCOLS
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
NOTIFY_TASK_FREQ = timedelta(hours=1)
|
|
|
|
|
|
def notification_key(user):
|
|
return key(f'notifs-{user.key.id()}')
|
|
|
|
|
|
def add_notification(user, obj):
|
|
"""Adds a notification for a given user.
|
|
|
|
The memcache key is ``notifs-{user id}``. The value is a space-separated list of
|
|
object URLs to notify the user of.
|
|
|
|
Uses gets/cas to create the cache entry if it doesn't exist.
|
|
|
|
Args:
|
|
user (models.User): the user to notify
|
|
obj (models.Object): the object to notify about
|
|
"""
|
|
key = notification_key(user)
|
|
obj_url = as1.get_url(obj.as1) or obj.key.id()
|
|
assert obj_url
|
|
|
|
if user.send_notifs != 'all':
|
|
return
|
|
|
|
# TODO: remove to launch
|
|
if (user.key.id() not in common.BETA_USER_IDS
|
|
and not (appengine_info.DEBUG or appengine_info.LOCAL_SERVER)):
|
|
return
|
|
|
|
if not util.is_web(obj_url):
|
|
logger.info(f'Dropping non-URL notif {obj_url} for {user.key.id()}')
|
|
return
|
|
|
|
logger.info(f'Adding notif {obj_url} for {user.key.id()}')
|
|
|
|
if memcache.add(key, obj_url.encode()):
|
|
common.create_task(queue='notify', delay=NOTIFY_TASK_FREQ,
|
|
user_id=user.key.id(), protocol=user.LABEL)
|
|
else:
|
|
existing = memcache.get(key)
|
|
if existing and obj_url not in existing.decode().split():
|
|
# there's a race condition here if the notify task runs between the gets
|
|
# call above and this append call, since there won't be a value in
|
|
# memcache, so append will do nothing. should be rare.
|
|
#
|
|
# gets/cas wouldn't make it any easier; we'd still need to keep retrying
|
|
# until we have a get/append or gets/cas that no one else writes between.
|
|
memcache.append(key, (' ' + obj_url).encode())
|
|
|
|
|
|
def get_notifications(user, clear=False):
|
|
"""Gets enqueued notifications for a given user.
|
|
|
|
The memcache key is ``notifs-{user id}``.
|
|
|
|
Args:
|
|
user (models.User)
|
|
clear (bool): clear notifications from memcache after fetching them
|
|
|
|
Returns:
|
|
list of str: URLs to notify the user of; possibly empty
|
|
"""
|
|
key = notification_key(user)
|
|
notifs = memcache.get(key, default=b'').decode().strip().split()
|
|
|
|
if notifs and clear:
|
|
memcache.delete(key)
|
|
|
|
return notifs
|
|
|
|
|
|
@cloud_tasks_only()
|
|
def notify_task():
|
|
"""Task handler for sending a notification DM to a user.
|
|
|
|
Fetches notifications from memcache.
|
|
|
|
Parameters:
|
|
user_id (str): ID of the user to send notifications to
|
|
protocol (str): Protocol label the user is on
|
|
"""
|
|
common.log_request()
|
|
|
|
proto = PROTOCOLS[request.form['protocol']]
|
|
user_id = request.form['user_id']
|
|
|
|
if not (user := proto.get_by_id(user_id)):
|
|
logger.info(f"Couldn't load user {user_id}")
|
|
return '', 204
|
|
|
|
if not (notifs := get_notifications(user, clear=True)):
|
|
logger.info(f'No notifications for {user_id}')
|
|
return '', 204
|
|
|
|
from_proto_label = (user.enabled_protocols[0] if user.enabled_protocols
|
|
else user.DEFAULT_ENABLED_PROTOCOLS[0] if user.DEFAULT_ENABLED_PROTOCOLS
|
|
else None)
|
|
if not from_proto_label:
|
|
logger.info(f"User {user_id} isn't enabled")
|
|
return '', 204
|
|
|
|
message = f"<p>Hi! Here are your recent interactions from people who aren't bridged into {user.PHRASE}:\n<ul>\n"
|
|
for url in notifs:
|
|
message += f'<li>{util.pretty_link(url)}\n'
|
|
message += "</ul>\n<p>To disable these messages, reply with the text 'mute'."
|
|
|
|
logger.info(f'sending notifications DM for {user_id}')
|
|
dms.maybe_send(from_proto=PROTOCOLS[from_proto_label], to_user=user, text=message)
|
|
|
|
return '', 200
|