From b113521f94005a20ab7e6424528a27c4d791fe44 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Mon, 4 Dec 2023 10:28:45 -0800 Subject: [PATCH] Web: start on Superfeedr subscribe for #550 --- .gitignore | 2 ++ tests/test_web.py | 16 ++++++++++++++-- web.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 6ba2f4a..9504b2e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ flask_secret_key opt_outs.txt private_notes service_account_creds.json +superfeedr_token +superfeedr_username TAGS diff --git a/tests/test_web.py b/tests/test_web.py index 87103dd..884d763 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -1,6 +1,6 @@ """Unit tests for webmention.py.""" import copy -from unittest.mock import patch +from unittest.mock import ANY, patch from flask import g, get_flashed_messages from google.cloud import ndb @@ -20,7 +20,8 @@ import common from common import CONTENT_TYPE_HTML from flask_app import app from models import Follower, Object -from web import TASKS_LOCATION, Web +import web +from web import SUPERFEEDR_PUSH_API, TASKS_LOCATION, Web from . import test_activitypub from .testutil import Fake, TestCase @@ -1769,6 +1770,17 @@ class WebTest(TestCase): """, headers={'Content-Type': atom.CONTENT_TYPE}) self.assertEqual(400, got.status_code) + @patch('oauth_dropins.webutil.appengine_info.LOCAL_SERVER', False) + def test_superfeedr_subscribe(self, mock_get, mock_post): + web.superfeedr_subscribe(self.user) + self.assert_req(mock_post, SUPERFEEDR_PUSH_API, data={ + 'hub.mode': 'subscribe', + 'hub.topic': 'https://user.com/feed', + 'hub.callback': 'http://localhost/superfeedr/notify/user.com', + 'format': 'atom', + 'retrieve': 'true', + }, auth=ANY) + def _test_verify(self, redirects, hcard, actor, redirects_error=None): self.user.has_redirects = False self.user.put() diff --git a/web.py b/web.py index d7583fa..3a026c3 100644 --- a/web.py +++ b/web.py @@ -18,6 +18,7 @@ from oauth_dropins.webutil.flask_util import cloud_tasks_only, error, flash from oauth_dropins.webutil.util import json_dumps, json_loads from oauth_dropins.webutil import webmention from requests import HTTPError, RequestException +from requests.auth import HTTPBasicAuth from werkzeug.exceptions import BadGateway, BadRequest, HTTPException, NotFound import common @@ -54,6 +55,10 @@ NON_TLDS = frozenset(( 'yml', )) +SUPERFEEDR_PUSH_API = 'https://push.superfeedr.com' +SUPERFEEDR_USERNAME = util.read('superfeedr_username') +SUPERFEEDR_TOKEN = util.read('superfeedr_token') + def is_valid_domain(domain): """Returns True if this is a valid domain we can use, False otherwise. @@ -586,6 +591,31 @@ def webmention_interactive(): return redirect('/', code=302) +def superfeedr_subscribe(user): + """Subscribes to a user's Atom or RSS feed in Superfeedr. + + Args: + user (Web) + """ + logger.info(f'Subscribing to {user.key.id()} via Superfeedr') + if appengine_info.LOCAL_SERVER: + logger.info(f"Skipping since we're local") + return + + auth = HTTPBasicAuth(SUPERFEEDR_USERNAME, SUPERFEEDR_TOKEN) + resp = util.requests_post(SUPERFEEDR_PUSH_API, auth=auth, data={ + 'hub.mode': 'subscribe', + 'hub.topic': f'{user.web_url()}feed', + 'hub.callback': common.host_url(f'/superfeedr/notify/{user.key.id()}'), + # TODO + # 'hub.secret': 'xxx', + 'format': 'atom', + 'retrieve': 'true', + }) + resp.raise_for_status() + return resp + + # generate/check per-user token for auth? # or https://documentation.superfeedr.com/subscribers.html#http-authentication ? @app.post(f'/superfeedr/notify/')