fix authors in RSS and Atom feeds

pull/602/head
Ryan Barrett 2023-07-28 15:49:29 -07:00
rodzic 8850e27374
commit b2b5383271
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
3 zmienionych plików z 84 dodań i 47 usunięć

Wyświetl plik

@ -5,7 +5,7 @@ import logging
import os import os
from flask import g, render_template, request 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.query import AND, OR
from google.cloud.ndb.stats import KindStat from google.cloud.ndb.stats import KindStat
from granary import as1, as2, atom, microformats2, rss from granary import as1, as2, atom, microformats2, rss
@ -147,19 +147,24 @@ def feed(protocol, id):
.fetch(PAGE_SIZE) .fetch(PAGE_SIZE)
activities = [obj.as1 for obj in objects if not obj.deleted] 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' fields = 'author', 'actor', 'object'
hydrate_ids = [id for id in itertools.chain( gets = []
*[[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 a in activities:
for field in fields: for field in fields:
id = a.get(field) val = as1.get_object(a, field)
if isinstance(id, str) and hydrated.get(id): if val and val.keys() <= set(['id']):
a[field] = hydrated[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 = { actor = {
'displayName': id, 'displayName': id,

Wyświetl plik

@ -1,15 +1,10 @@
"""Unit tests for pages.py.""" """Unit tests for pages.py."""
from google.cloud import ndb from google.cloud import ndb
from granary import atom, microformats2, rss from granary import atom, microformats2, rss
from granary.tests.test_as1 import (
ACTOR,
COMMENT,
NOTE,
)
from oauth_dropins.webutil import util from oauth_dropins.webutil import util
# import first so that Fake is defined before URL routes are registered # 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 activitypub import ActivityPub
from models import Object, Follower from models import Object, Follower
@ -24,11 +19,12 @@ ACTOR_WITH_PREFERRED_USERNAME = {
def contents(activities): 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): class PagesTest(TestCase):
EXPECTED = contents([COMMENT, NOTE]) EXPECTED = contents([COMMENT, NOTE, NOTE])
def setUp(self): def setUp(self):
super().setUp() super().setUp()
@ -254,19 +250,6 @@ class PagesTest(TestCase):
def test_feed_html(self): def test_feed_html(self):
self.add_objects() 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 with object (original post) in separate Object
repost = { repost = {
'objectType': 'activity', 'objectType': 'activity',
@ -277,15 +260,17 @@ class PagesTest(TestCase):
'objectType': 'note', 'objectType': 'note',
'content': 'biff', '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) self.store_object(id='fake:orig', our_as1=orig)
got = self.client.get('/web/user.com/feed') got = self.client.get('/web/user.com/feed')
self.assert_equals(200, got.status_code) 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))) 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): def test_feed_atom_empty(self):
got = self.client.get('/web/user.com/feed?format=atom') 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(200, got.status_code)
self.assert_equals(self.EXPECTED, contents(atom.atom_to_activities(got.text))) 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): def test_feed_rss_empty(self):
got = self.client.get('/web/user.com/feed?format=rss') got = self.client.get('/web/user.com/feed?format=rss')
self.assert_equals(200, got.status_code) self.assert_equals(200, got.status_code)
@ -309,6 +304,12 @@ class PagesTest(TestCase):
self.assert_equals(200, got.status_code) self.assert_equals(200, got.status_code)
self.assert_equals(self.EXPECTED, contents(rss.to_activities(got.text))) 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): def test_nodeinfo(self):
# just check that it doesn't crash # just check that it doesn't crash
self.client.get('/nodeinfo.json') self.client.get('/nodeinfo.json')

Wyświetl plik

@ -16,6 +16,7 @@ from flask import g
from google.cloud import ndb from google.cloud import ndb
from granary import as2 from granary import as2
from granary.tests.test_as1 import ( from granary.tests.test_as1 import (
ACTOR,
COMMENT, COMMENT,
MENTION, MENTION,
NOTE, NOTE,
@ -33,6 +34,25 @@ import protocol
logger = logging.getLogger(__name__) 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): class Fake(User, protocol.Protocol):
ABBREV = 'fa' ABBREV = 'fa'
@ -237,25 +257,36 @@ class TestCase(unittest.TestCase, testutil.Asserts):
users=[user], users=[user],
notify=[user], notify=[user],
feed=[user], feed=[user],
as2=as2.from_as1(NOTE)) our_as1=NOTE)
# different domain # post with mention
nope = ndb.Key(Web, 'nope.org')
self.store_object(id='b', self.store_object(id='b',
notify=[nope], notify=[user],
feed=[nope], feed=[user],
as2=as2.from_as1(MENTION)) our_as1=MENTION)
# reply # reply
self.store_object(id='d', self.store_object(id='d',
notify=[user], notify=[user],
feed=[user], feed=[user],
as2=as2.from_as1(COMMENT)) our_as1=COMMENT)
# not feed/notif # 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 # deleted
self.store_object(id='f', self.store_object(id='f',
notify=[user], notify=[user],
feed=[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 @staticmethod
def store_object(**kwargs): def store_object(**kwargs):