kopia lustrzana https://github.com/snarfed/bridgy-fed
fix authors in RSS and Atom feeds
rodzic
8850e27374
commit
b2b5383271
31
pages.py
31
pages.py
|
@ -5,7 +5,7 @@ import logging
|
|||
import os
|
||||
|
||||
from flask import g, render_template, request
|
||||
from google.cloud import ndb
|
||||
from google.cloud.ndb import tasklets
|
||||
from google.cloud.ndb.query import AND, OR
|
||||
from google.cloud.ndb.stats import KindStat
|
||||
from granary import as1, as2, atom, microformats2, rss
|
||||
|
@ -147,19 +147,24 @@ def feed(protocol, id):
|
|||
.fetch(PAGE_SIZE)
|
||||
activities = [obj.as1 for obj in objects if not obj.deleted]
|
||||
|
||||
# fill in authors, actors, objects stored in their own Objects
|
||||
# hydrate authors, actors, objects from stored Objects
|
||||
fields = 'author', 'actor', 'object'
|
||||
hydrate_ids = [id for id in itertools.chain(
|
||||
*[[a[f] for f in fields if isinstance(a.get(f), str)]
|
||||
for a in activities])]
|
||||
if hydrate_ids:
|
||||
keys = [ndb.Key(Object, id) for id in hydrate_ids]
|
||||
hydrated = {o.key.id(): o.as1 for o in ndb.get_multi(keys) if o}
|
||||
for a in activities:
|
||||
for field in fields:
|
||||
id = a.get(field)
|
||||
if isinstance(id, str) and hydrated.get(id):
|
||||
a[field] = hydrated[id]
|
||||
gets = []
|
||||
for a in activities:
|
||||
for field in fields:
|
||||
val = as1.get_object(a, field)
|
||||
if val and val.keys() <= set(['id']):
|
||||
def hydrate(a, f):
|
||||
def maybe_set(future):
|
||||
if future.result() and future.result().as1:
|
||||
a[f] = future.result().as1
|
||||
return maybe_set
|
||||
|
||||
future = Object.get_by_id_async(val['id'])
|
||||
future.add_done_callback(hydrate(a, field))
|
||||
gets.append(future)
|
||||
|
||||
tasklets.wait_all(gets)
|
||||
|
||||
actor = {
|
||||
'displayName': id,
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
"""Unit tests for pages.py."""
|
||||
from google.cloud import ndb
|
||||
from granary import atom, microformats2, rss
|
||||
from granary.tests.test_as1 import (
|
||||
ACTOR,
|
||||
COMMENT,
|
||||
NOTE,
|
||||
)
|
||||
from oauth_dropins.webutil import util
|
||||
|
||||
# import first so that Fake is defined before URL routes are registered
|
||||
from .testutil import Fake, TestCase
|
||||
from .testutil import Fake, TestCase, ACTOR, COMMENT, MENTION, NOTE
|
||||
|
||||
from activitypub import ActivityPub
|
||||
from models import Object, Follower
|
||||
|
@ -24,11 +19,12 @@ ACTOR_WITH_PREFERRED_USERNAME = {
|
|||
|
||||
|
||||
def contents(activities):
|
||||
return [(a.get('object') or a)['content'] for a in activities]
|
||||
return [(a.get('object') or a)['content'].splitlines()[0]
|
||||
for a in activities]
|
||||
|
||||
|
||||
class PagesTest(TestCase):
|
||||
EXPECTED = contents([COMMENT, NOTE])
|
||||
EXPECTED = contents([COMMENT, NOTE, NOTE])
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
@ -254,19 +250,6 @@ class PagesTest(TestCase):
|
|||
def test_feed_html(self):
|
||||
self.add_objects()
|
||||
|
||||
# note with author in separate Object
|
||||
note_2 = {
|
||||
'objectType': 'note',
|
||||
'content': 'foo',
|
||||
'author': 'fake:alice',
|
||||
}
|
||||
alice = {
|
||||
'displayName': 'Ms Alice Macbeth',
|
||||
}
|
||||
user = ndb.Key(Web, 'user.com')
|
||||
self.store_object(id='fake:note_2', feed=[user], our_as1=note_2)
|
||||
self.store_object(id='fake:alice', our_as1=alice)
|
||||
|
||||
# repost with object (original post) in separate Object
|
||||
repost = {
|
||||
'objectType': 'activity',
|
||||
|
@ -277,15 +260,17 @@ class PagesTest(TestCase):
|
|||
'objectType': 'note',
|
||||
'content': 'biff',
|
||||
}
|
||||
self.store_object(id='fake:repost', feed=[user], our_as1=repost)
|
||||
self.store_object(id='fake:repost', feed=[self.user.key], our_as1=repost)
|
||||
self.store_object(id='fake:orig', our_as1=orig)
|
||||
|
||||
got = self.client.get('/web/user.com/feed')
|
||||
self.assert_equals(200, got.status_code)
|
||||
self.assert_equals(self.EXPECTED + ['foo', 'biff'],
|
||||
self.assert_equals(['biff'] + self.EXPECTED,
|
||||
contents(microformats2.html_to_activities(got.text)))
|
||||
self.assertIn('Ms Alice Macbeth', got.text)
|
||||
self.assertIn('biff', got.text)
|
||||
|
||||
# NOTE's and MENTION's authors; check for two instances
|
||||
bob = '<a class="p-name u-url" href="https://plus.google.com/bob">Bob</a>'
|
||||
assert got.text.index(bob) != got.text.rindex(bob)
|
||||
|
||||
def test_feed_atom_empty(self):
|
||||
got = self.client.get('/web/user.com/feed?format=atom')
|
||||
|
@ -298,6 +283,16 @@ class PagesTest(TestCase):
|
|||
self.assert_equals(200, got.status_code)
|
||||
self.assert_equals(self.EXPECTED, contents(atom.atom_to_activities(got.text)))
|
||||
|
||||
# NOTE's and MENTION's authors; check for two instances
|
||||
bob = """
|
||||
<uri>https://plus.google.com/bob</uri>
|
||||
|
||||
<name>Bob</name>
|
||||
"""
|
||||
assert got.text.index(bob) != got.text.rindex(bob)
|
||||
# COMMENT's author
|
||||
self.assertIn('Dr. Eve', got.text)
|
||||
|
||||
def test_feed_rss_empty(self):
|
||||
got = self.client.get('/web/user.com/feed?format=rss')
|
||||
self.assert_equals(200, got.status_code)
|
||||
|
@ -309,6 +304,12 @@ class PagesTest(TestCase):
|
|||
self.assert_equals(200, got.status_code)
|
||||
self.assert_equals(self.EXPECTED, contents(rss.to_activities(got.text)))
|
||||
|
||||
# NOTE's and MENTION's authors; check for two instances
|
||||
bob = '<author>- (Bob)</author>'
|
||||
assert got.text.index(bob) != got.text.rindex(bob)
|
||||
# COMMENT's author
|
||||
self.assertIn('Dr. Eve', got.text)
|
||||
|
||||
def test_nodeinfo(self):
|
||||
# just check that it doesn't crash
|
||||
self.client.get('/nodeinfo.json')
|
||||
|
|
|
@ -16,6 +16,7 @@ from flask import g
|
|||
from google.cloud import ndb
|
||||
from granary import as2
|
||||
from granary.tests.test_as1 import (
|
||||
ACTOR,
|
||||
COMMENT,
|
||||
MENTION,
|
||||
NOTE,
|
||||
|
@ -33,6 +34,25 @@ import protocol
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
NOTE = {
|
||||
**NOTE,
|
||||
# bare string author id
|
||||
'author': ACTOR['id'],
|
||||
}
|
||||
MENTION = {
|
||||
**MENTION,
|
||||
# author object with just id
|
||||
'author': {'id': ACTOR['id']},
|
||||
}
|
||||
COMMENT = {
|
||||
**COMMENT,
|
||||
# full author object
|
||||
'author': {
|
||||
**ACTOR,
|
||||
'displayName': 'Dr. Eve',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class Fake(User, protocol.Protocol):
|
||||
ABBREV = 'fa'
|
||||
|
@ -237,25 +257,36 @@ class TestCase(unittest.TestCase, testutil.Asserts):
|
|||
users=[user],
|
||||
notify=[user],
|
||||
feed=[user],
|
||||
as2=as2.from_as1(NOTE))
|
||||
# different domain
|
||||
nope = ndb.Key(Web, 'nope.org')
|
||||
our_as1=NOTE)
|
||||
# post with mention
|
||||
self.store_object(id='b',
|
||||
notify=[nope],
|
||||
feed=[nope],
|
||||
as2=as2.from_as1(MENTION))
|
||||
notify=[user],
|
||||
feed=[user],
|
||||
our_as1=MENTION)
|
||||
# reply
|
||||
self.store_object(id='d',
|
||||
notify=[user],
|
||||
feed=[user],
|
||||
as2=as2.from_as1(COMMENT))
|
||||
our_as1=COMMENT)
|
||||
# not feed/notif
|
||||
self.store_object(id='e', users=[user], as2=as2.from_as1(NOTE))
|
||||
self.store_object(id='e',
|
||||
users=[user],
|
||||
our_as1=NOTE)
|
||||
# deleted
|
||||
self.store_object(id='f',
|
||||
notify=[user],
|
||||
feed=[user],
|
||||
as2=as2.from_as1(NOTE), deleted=True)
|
||||
our_as1=NOTE,
|
||||
deleted=True)
|
||||
# different domain
|
||||
nope = ndb.Key(Web, 'nope.org')
|
||||
self.store_object(id='g',
|
||||
notify=[nope],
|
||||
feed=[nope],
|
||||
our_as1=MENTION)
|
||||
|
||||
# actor whose id is in NOTE.author
|
||||
self.store_object(id=ACTOR['id'], our_as1=ACTOR)
|
||||
|
||||
@staticmethod
|
||||
def store_object(**kwargs):
|
||||
|
|
Ładowanie…
Reference in New Issue