diff --git a/app.py b/app.py index 2ff3369..4cdea6f 100644 --- a/app.py +++ b/app.py @@ -6,7 +6,7 @@ registered. from flask_app import app # import all modules to register their Flask handlers -import activitypub, atproto, convert, follow, pages, redirect, superfeedr, ui, webfinger, web +import activitypub, atproto, convert, follow, pages, redirect, ui, webfinger, web import models models.reset_protocol_properties() diff --git a/docs/source/modules.rst b/docs/source/modules.rst index b6f15af..0068904 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -55,11 +55,6 @@ render .. automodule:: render :exclude-members: __eq__, __getnewargs__, __getstate__, __hash__, __new__, __repr__, __str__, __weakref__ -superfeedr ----------- -.. automodule:: superfeedr - :exclude-members: __eq__, __getnewargs__, __getstate__, __hash__, __new__, __repr__, __str__, __weakref__ - web --- .. automodule:: web diff --git a/superfeedr.py b/superfeedr.py deleted file mode 100644 index 9592a4c..0000000 --- a/superfeedr.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Superfeedr callback handlers. - -Not really sure what this will be yet. - -* https://github.com/snarfed/bridgy-fed/issues/550 -* https://github.com/snarfed/bridgy-fed/issues/18#issuecomment-430731476 -* https://documentation.superfeedr.com/publishers.html -""" -import logging - -from flask import request - -from flask_app import app - -logger = logging.getLogger(__name__) - - -@app.route(r'/superfeedr/', methods=['GET', 'POST']) -@app.route(r'/superfeedr/', methods=['GET', 'POST']) -def superfeedr(_=None): - """Superfeedr subscription callback handler. - - https://documentation.superfeedr.com/publishers.html#subscription-callback - """ - logger.info(f'Got:\n{request.get_data(as_text=True)}') - return '', 204 diff --git a/tests/test_web.py b/tests/test_web.py index c08320f..4f082e7 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -4,7 +4,7 @@ from unittest.mock import patch from flask import g, get_flashed_messages from google.cloud import ndb -from granary import as1, as2, microformats2 +from granary import as1, as2, atom, microformats2 from oauth_dropins.webutil import util from oauth_dropins.webutil import appengine_info from oauth_dropins.webutil.testutil import NOW, requests_response @@ -1735,6 +1735,38 @@ class WebTest(TestCase): "WARNING:models:actor https://user.com/ isn't https://user.com/like's author or actor ['https://eve.com/']", logs.output) + @patch('oauth_dropins.webutil.appengine_config.tasks_client.create_task') + def test_superfeedr_make_task(self, mock_create_task, *_): + common.RUN_TASKS_INLINE = False + + got = self.post('/superfeedr/notify/user.com', data="""\ + + +https://user.com/post +I hereby ☕ post. + +""", headers={'Content-Type': atom.CONTENT_TYPE}) + self.assertEqual(200, got.status_code) + self.assert_task(mock_create_task, 'receive', '/queue/receive', + obj=Object(id='https://user.com/post').key.urlsafe(), + authed_as='user.com') + + def test_superfeedr_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_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) + 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 7458f68..0330fba 100644 --- a/web.py +++ b/web.py @@ -9,7 +9,7 @@ from urllib.parse import quote, urlencode, urljoin, urlparse from flask import g, redirect, render_template, request from google.cloud import ndb from google.cloud.ndb import ComputedProperty -from granary import as1, as2, microformats2 +from granary import as1, as2, atom, microformats2 import mf2util from oauth_dropins.webutil import flask_util, util from oauth_dropins.webutil.appengine_config import tasks_client @@ -581,6 +581,38 @@ def webmention_interactive(): return redirect('/', code=302) +# 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) + + text = request.get_data(as_text=True) + obj = Object(atom=text) + 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=text, source_protocol=Web.ABBREV) + common.create_task(queue='receive', obj=obj.key.urlsafe(), authed_as=domain) + + return 'OK' + + @app.post('/queue/webmention') @cloud_tasks_only def webmention_task(): @@ -593,9 +625,9 @@ def webmention_task(): logger.info(f'webmention from {domain}') user = Web.get_by_id(domain) - logger.info(f'User: {user.key}') if not user: error(f'No user found for domain {domain}', status=304) + logger.info(f'User: {user.key}') # fetch source page try: