diff --git a/CHANGELOG.md b/CHANGELOG.md index c78dee8..beae16b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -* Inbound Activitypub payloads are now processed by calamus (https://github.com/SwissDataScienceCenter/calamus), +* Activitypub payloads are now processed by calamus (https://github.com/SwissDataScienceCenter/calamus), which is a jsonld processor based on marshmallow. * For performance, requests_cache has been added. It pulls a redis configuration from django if one exists or diff --git a/federation/entities/activitypub/constants.py b/federation/entities/activitypub/constants.py index 9771277..b5d25ae 100644 --- a/federation/entities/activitypub/constants.py +++ b/federation/entities/activitypub/constants.py @@ -15,6 +15,8 @@ CONTEXT = [CONTEXT_ACTIVITYSTREAMS, CONTEXT_LD_SIGNATURES] CONTEXT_DICT = {} for ctx in [CONTEXT_DIASPORA, CONTEXT_HASHTAG, CONTEXT_MANUALLY_APPROVES_FOLLOWERS, CONTEXT_SENSITIVE, CONTEXT_PYTHON_FEDERATION]: CONTEXT_DICT.update(ctx) +CONTEXT_SETS = {prop: {'@id': f'as:{prop}', '@container': '@set'} for prop in ['to', 'cc', 'tag', 'attachment']} +CONTEXT_DICT.update(CONTEXT_SETS) CONTEXT.append(CONTEXT_DICT) NAMESPACE_PUBLIC = "https://www.w3.org/ns/activitystreams#Public" diff --git a/federation/entities/activitypub/models.py b/federation/entities/activitypub/models.py index 38c94e5..7eb4f37 100644 --- a/federation/entities/activitypub/models.py +++ b/federation/entities/activitypub/models.py @@ -15,7 +15,7 @@ from marshmallow.utils import EXCLUDE, missing from pyld import jsonld import requests_cache as rc -from federation.entities.activitypub.constants import CONTEXT, NAMESPACE_PUBLIC +from federation.entities.activitypub.constants import CONTEXT, CONTEXT_SETS, NAMESPACE_PUBLIC from federation.entities.mixins import BaseEntity, RawContentMixin from federation.entities.utils import get_base_attributes, get_profile from federation.outbound import handle_send @@ -294,8 +294,8 @@ class Object(BaseEntity, metaclass=JsonLDAnnotation): signature = MixedField(sec.signature, nested = 'SignatureSchema') start_time = fields.DateTime(as2.startTime, add_value_types=True) updated = fields.DateTime(as2.updated, add_value_types=True) - to = fields.List(as2.to, cls_or_instance=IRI(as2.to)) - cc = fields.List(as2.cc, cls_or_instance=IRI(as2.cc)) + to = fields.List(as2.to, cls_or_instance=fields.String(as2.to)) + cc = fields.List(as2.cc, cls_or_instance=fields.String(as2.cc)) media_type = fields.String(as2.mediaType) source = CompactedDict(as2.source) @@ -405,6 +405,8 @@ class Object(BaseEntity, metaclass=JsonLDAnnotation): upd.update(val) if not idx and upd: ctx.append(upd) + # for to and cc fields to be processed as strings + ctx.append(CONTEXT_SETS) data['@context'] = ctx return data @@ -634,7 +636,6 @@ class Person(Object, base.Profile): self.handle = self.finger def to_as2(self): - #self.id = self.id.rstrip('/') # TODO: sort out the trailing / business self.followers = f'{with_slash(self.id)}followers/' self.following = f'{with_slash(self.id)}following/' self.outbox = f'{with_slash(self.id)}outbox/' @@ -648,6 +649,7 @@ class Person(Object, base.Profile): actor_id=self.id, created_at=self.times.get('updated'), object_=self, + to=self.to, ) return super().to_as2() @@ -1300,13 +1302,8 @@ def extract_and_validate(entity): if hasattr(entity, "extract_mentions"): entity.extract_mentions() - # Extract reply ids - if getattr(entity, 'replies', None): - entity._replies = extract_reply_ids(getattr(entity.replies, 'first', [])) - - -def extract_reply_ids(replies, visited=[]): +def extract_replies(replies, visited=[]): objs = [] items = getattr(replies, 'items', []) if items and not isinstance(items, list): items = [items] @@ -1316,7 +1313,7 @@ def extract_reply_ids(replies, visited=[]): resp = retrieve_and_parse_document(replies.next_) if resp: visited.append(replies.next_) - objs += extract_reply_ids(resp, visited) + objs += extract_replies(resp, visited) return objs diff --git a/federation/entities/mixins.py b/federation/entities/mixins.py index 94ace2d..e3ecdb2 100644 --- a/federation/entities/mixins.py +++ b/federation/entities/mixins.py @@ -286,11 +286,15 @@ class RawContentMixin(BaseEntity): if not matches: return for mention in matches: + handle = None splits = mention.split(";") if len(splits) == 1: - self._mentions.add(splits[0].strip(' }').lstrip('@{')) + handle = splits[0].strip(' }').lstrip('@{') elif len(splits) == 2: - self._mentions.add(splits[1].strip(' }')) + handle = splits[1].strip(' }') + if handle: + self._mentions.add(handle) + self.raw_content = self.raw_content.replace(mention, '@'+handle) class OptionalRawContentMixin(RawContentMixin): diff --git a/federation/tests/entities/activitypub/test_entities.py b/federation/tests/entities/activitypub/test_entities.py index a97c96f..b3099b7 100644 --- a/federation/tests/entities/activitypub/test_entities.py +++ b/federation/tests/entities/activitypub/test_entities.py @@ -123,12 +123,12 @@ class TestEntitiesConvertToAS2: 'type': 'Create', 'id': 'http://127.0.0.1:8000/post/123456/#create', 'actor': 'http://127.0.0.1:8000/profile/123456/', - 'cc': 'https://http://127.0.0.1:8000/profile/123456/followers/', - 'to': 'as:Public', + 'cc': ['https://http://127.0.0.1:8000/profile/123456/followers/'], + 'to': ['https://www.w3.org/ns/activitystreams#Public'], 'object': { 'id': 'http://127.0.0.1:8000/post/123456/', - 'cc': 'https://http://127.0.0.1:8000/profile/123456/followers/', - 'to': 'as:Public', + 'cc': ['https://http://127.0.0.1:8000/profile/123456/followers/'], + 'to': ['https://www.w3.org/ns/activitystreams#Public'], 'type': 'Note', 'attributedTo': 'http://127.0.0.1:8000/profile/123456/', 'content': '