kopia lustrzana https://github.com/snarfed/bridgy-fed
web: start removing superfeedr code
rodzic
56b44ac8a5
commit
6025925c26
|
@ -23,7 +23,7 @@ from common import CONTENT_TYPE_HTML
|
|||
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 @@ class WebTest(TestCase):
|
|||
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="""\
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<entry xmlns="http://www.w3.org/2005/Atom">
|
||||
<content>I hereby ☕ post.</content>
|
||||
</entry>
|
||||
""", 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 = ["""\
|
||||
<author>
|
||||
<uri>http://my/site</uri>
|
||||
<name>Mr. Bob</name>
|
||||
</author>
|
||||
<id>domain.tld:09/05/03-1</id>
|
||||
<uri>http://domain.tld/entry/1</uri>
|
||||
<published>2013-04-21T14:00:40+02:00</published>
|
||||
<updated>2013-04-21T14:00:40+02:00</updated>
|
||||
<title>Entry published on hour ago</title>
|
||||
<content type="text">Entry published on hour ago when it was shiny outside, but now it's raining</content>
|
||||
<summary type="text">Entry published on hour ago...</summary>
|
||||
""", """\
|
||||
<author>
|
||||
<uri>http://eve/</uri>
|
||||
</author>
|
||||
<id>http://domain.tld/entry/2</id>
|
||||
<title>Second post</title>
|
||||
<content type="text">Something else</content>
|
||||
"""]
|
||||
|
||||
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"""\
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
<entry>
|
||||
{entries[0]}
|
||||
</entry>
|
||||
<entry>
|
||||
{entries[1]}
|
||||
</entry>
|
||||
</feed>
|
||||
""", 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()
|
||||
|
|
111
web.py
111
web.py
|
@ -56,14 +56,10 @@ NON_TLDS = frozenset((
|
|||
'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/<regex("{DOMAIN_RE}"):domain>')
|
||||
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():
|
||||
|
|
Ładowanie…
Reference in New Issue