diff --git a/tests/test_web.py b/tests/test_web.py index 22c96389..0bd153e0 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -23,7 +23,7 @@ from flask_app import app from models import Follower, Object import web -from web import SUPERFEEDR_PUSH_API, TASKS_LOCATION, Web +from web import TASKS_LOCATION, Web from . import test_activitypub from .testutil import Fake, TestCase @@ -1962,129 +1962,6 @@ def test_poll_feed_rss(self, mock_create_task, mock_get, _): self.assert_task(mock_create_task, 'poll-feed', '/queue/poll-feed', domain='user.com', eta_seconds=expected_eta) - def test_superfeedr_notify_no_user(self, *_): - orig_count = Object.query().count() - - got = self.post('/webmention', data={'source': 'https://nope.com/post'}) - self.assertEqual(400, got.status_code) - self.assertEqual(orig_count, Object.query().count()) - - def test_superfeedr_notify_no_id(self, *mocks): - got = self.post('/superfeedr/notify/user.com', data="""\ - - -I hereby ☕ post. - -""", headers={'Content-Type': atom.CONTENT_TYPE}) - self.assertEqual(400, got.status_code) - - @patch('oauth_dropins.webutil.appengine_config.tasks_client.create_task') - def test_maybe_superfeedr_subscribe(self, mock_create_task, mock_get, mock_post): - common.RUN_TASKS_INLINE = False - - entries = ["""\ - - http://my/site - Mr. Bob - - domain.tld:09/05/03-1 - http://domain.tld/entry/1 - 2013-04-21T14:00:40+02:00 - 2013-04-21T14:00:40+02:00 - Entry published on hour ago - Entry published on hour ago when it was shiny outside, but now it's raining - Entry published on hour ago... -""", """\ - - http://eve/ - - http://domain.tld/entry/2 - Second post - Something else -"""] - - self.assertIsNone(self.user.superfeedr_subscribed) - self.user.obj.mf2 = ACTOR_MF2_REL_FEED_URL - self.user.has_redirects = False - - # https://documentation.superfeedr.com/schema.html#entries - mock_post.return_value = requests_response(f"""\ - - - -{entries[0]} - - -{entries[1]} - - -""", content_type=atom.CONTENT_TYPE) - - web.maybe_superfeedr_subscribe(self.user) - self.assert_req(mock_post, SUPERFEEDR_PUSH_API, data={ - 'hub.mode': 'subscribe', - 'hub.topic': 'https://foo/atom', - 'hub.callback': 'http://localhost/superfeedr/notify/user.com', - 'format': 'atom', - 'retrieve': 'true', - }, auth=ANY) - self.assertEqual(NOW, self.user.key.get().superfeedr_subscribed) - self.assertEqual('https://foo/atom', - self.user.key.get().superfeedr_subscribed_feed) - - obj = Object.get_by_id('http://domain.tld/entry/1') - self.assertIn(entries[0], obj.atom) - self.assert_task(mock_create_task, 'receive', '/queue/receive', - obj=obj.key.urlsafe(), authed_as='user.com') - - obj = Object.get_by_id('http://domain.tld/entry/2') - self.assertIn(entries[1], obj.atom) - self.assert_task(mock_create_task, 'receive', '/queue/receive', - obj=obj.key.urlsafe(), authed_as='user.com') - - def test_maybe_superfeedr_subscribe_no_feed(self, mock_get, mock_post): - self.user.obj.mf2 = ACTOR_MF2 # no rel-urls - web.maybe_superfeedr_subscribe(self.user) - self.assertIsNone(self.user.key.get().superfeedr_subscribed) - - def test_maybe_superfeedr_subscribe_already_subscribed(self, mock_get, mock_post): - self.user.superfeedr_subscribed = NOW - self.user.put() - web.maybe_superfeedr_subscribe(self.user) - # should be a noop - - def test_maybe_superfeedr_unsubscribe(self, mock_get, mock_post): - appengine_info.LOCAL_SERVER = False - - self.user.superfeedr_subscribed = NOW - self.user.superfeedr_subscribed_feed = 'http://feed' - self.user.put() - - mock_post.return_value = requests_response('OK') - - web.maybe_superfeedr_unsubscribe(self.user) - self.assert_req(mock_post, SUPERFEEDR_PUSH_API, data={ - 'hub.mode': 'unsubscribe', - 'hub.topic': 'http://feed', - 'hub.callback': 'http://localhost/superfeedr/notify/user.com', - }, auth=ANY) - - def test_maybe_superfeedr_unsubscribe_not_subscribed(self, mock_get, mock_post): - appengine_info.LOCAL_SERVER = False - - web.maybe_superfeedr_unsubscribe(self.user) - mock_post.assert_not_called() - - def test_maybe_superfeedr_unsubscribe_last_webmention_later(self, _, mock_post): - appengine_info.LOCAL_SERVER = False - - self.user.superfeedr_subscribed = NOW - self.user.last_webmention_in = NOW + timedelta(days=1) - self.user.put() - - web.maybe_superfeedr_unsubscribe(self.user) - mock_post.assert_not_called() - 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 bff7bbd4..09f5f0d1 100644 --- a/web.py +++ b/web.py @@ -56,14 +56,10 @@ 'yml', )) -SUPERFEEDR_PUSH_API = 'https://push.superfeedr.com' -SUPERFEEDR_USERNAME = util.read('superfeedr_username') -SUPERFEEDR_TOKEN = util.read('superfeedr_token') FEED_TYPES = { atom.CONTENT_TYPE.split(';')[0]: 'atom', rss.CONTENT_TYPE.split(';')[0]: 'rss', } - MIN_FEED_POLL_PERIOD = timedelta(hours=2) MAX_FEED_POLL_PERIOD = timedelta(weeks=1) @@ -626,71 +622,6 @@ def maybe_superfeedr_subscribe(user): logger.info(f"User {user.key.id()} has no mf2, can't subscribe via Superfeedr") return - # discover feed - for url, info in user.obj.mf2.get('rel-urls', {}).items(): - if ('alternate' in info.get('rels', []) - and info.get('type', '').split(';')[0] in FEED_TYPES.keys()): - break - else: - logger.info(f"User {user.key.id()} has no feed URL, can't subscribe") - return - - # subscribe - logger.info(f'Subscribing to {url} 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': url, - 'hub.callback': common.host_url(f'/superfeedr/notify/{user.key.id()}'), - # TODO - # 'hub.secret': 'xxx', - 'format': 'atom', - 'retrieve': 'true', - }) - try: - resp.raise_for_status() - except BaseException as e: - util.interpret_http_exception(e) - return - - user.superfeedr_subscribed = util.now() - user.superfeedr_subscribed_feed = url - user.put() - - # handle feed entries (posts) - if (resp.headers.get('Content-Type', '').split(';')[0] == - atom.CONTENT_TYPE.split(';')[0]): - return _superfeedr_notify(resp.text, user=user) - - -def maybe_superfeedr_unsubscribe(user): - """Subscribes to a user's Atom or RSS feed in Superfeedr. - - Args: - user (Web) - """ - if (not user.superfeedr_subscribed - or not user.superfeedr_subscribed_feed - or (user.last_webmention_in - and user.last_webmention_in > user.superfeedr_subscribed) - or appengine_info.LOCAL_SERVER): - return - - # unsubscribe - logger.info(f'Unsubscribing from Superfeedr for {user.superfeedr_subscribed_feed}') - - auth = HTTPBasicAuth(SUPERFEEDR_USERNAME, SUPERFEEDR_TOKEN) - resp = util.requests_post(SUPERFEEDR_PUSH_API, auth=auth, data={ - 'hub.mode': 'unsubscribe', - 'hub.topic': user.superfeedr_subscribed_feed, - 'hub.callback': common.host_url(f'/superfeedr/notify/{user.key.id()}'), - }) - resp.raise_for_status() - @app.post(f'/queue/poll-feed') def poll_feed_task(): @@ -765,48 +696,6 @@ def poll_feed_task(): return 'OK' -# generate/check per-user token for auth? -# or https://documentation.superfeedr.com/subscribers.html#http-authentication ? -@app.post(f'/superfeedr/notify/') -def superfeedr_notify(domain): - """Superfeedr notification handler. - - https://documentation.superfeedr.com/publishers.html#subscription-callback - """ - logger.info(f'Got:\n{request.get_data(as_text=True)}') - - type = request.headers.get('Content-Type', '').split(';')[0] - if type != atom.CONTENT_TYPE.split(';')[0]: - error(f'Expected Content-Type {atom.CONTENT_TYPE}, got {type}', status=406) - - user = Web.get_by_id(domain) - if not user: - error(f'No user found for domain {domain}', status=304) - - return _superfeedr_notify(request.get_data(as_text=True), user=user) - - -def _superfeedr_notify(doc, user): - """Creates :class:`Object`s and ``receive`` tasks for an Atom feed or entry. - - Args: - doc (str): Atom document - user (Web): user this feed came from - """ - for entry in atom.extract_entries(doc): - obj_as1 = Object(atom=entry).as1 - logger.info(f'Converted to AS1: {json_dumps(obj_as1, indent=2)}') - id = obj_as1.get('id') - if not id: - return error('No id or URL!') - - obj = Object.get_or_create(id=id, atom=entry, source_protocol=Web.ABBREV) - common.create_task(queue='receive', obj=obj.key.urlsafe(), - authed_as=user.key.id()) - - return 'OK' - - @app.post('/queue/webmention') @cloud_tasks_only def webmention_task():