diff --git a/CHANGELOG.md b/CHANGELOG.md index db52e7a..8b113d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ The country information is fetched using the free `ipdata.co` service. NOTE! This service is rate limited to 1500 requests per day. +* Extract mentions from Diaspora payloads that have text content. The mentions will be available in the entity as `_mentions` which is a set of Diaspora ID's in URI format. + ### Changed * Send outbound Diaspora payloads in new format. Remove possibility to generate legacy MagicEnvelope payloads. ([related issue](https://github.com/jaywink/federation/issues/82)) diff --git a/federation/entities/base.py b/federation/entities/base.py index df4e284..6daffc1 100644 --- a/federation/entities/base.py +++ b/federation/entities/base.py @@ -23,6 +23,7 @@ class BaseEntity: def __init__(self, *args, **kwargs): self._required = [] self._children = [] + self._mentions = set() for key, value in kwargs.items(): if hasattr(self, key): setattr(self, key, value) @@ -31,6 +32,9 @@ class BaseEntity: self.__class__.__name__, key )) + def extract_mentions(self): + return set() + @property def id(self): """Global network ID. diff --git a/federation/entities/diaspora/entities.py b/federation/entities/diaspora/entities.py index 691e1c1..39ed486 100644 --- a/federation/entities/diaspora/entities.py +++ b/federation/entities/diaspora/entities.py @@ -1,4 +1,5 @@ import importlib +import re from lxml import etree @@ -8,7 +9,7 @@ from federation.entities.base import ( from federation.entities.diaspora.utils import format_dt, struct_to_xml, get_base_attributes, add_element_to_doc from federation.exceptions import SignatureVerificationError from federation.protocols.diaspora.signatures import verify_relayable_signature, create_relayable_signature -from federation.utils.diaspora import retrieve_and_parse_profile +from federation.utils.diaspora import retrieve_and_parse_profile, generate_diaspora_profile_id CLASS_TO_TAG_MAPPING = { Comment: "comment", @@ -27,6 +28,21 @@ class DiasporaEntityMixin(BaseEntity): # Normally outbound document is generated from entity. Store one here if at some point we already have a doc outbound_doc = None + def extract_mentions(self): + """ + Extract mentions from an entity with ``raw_content``. + + :return: set + """ + if not hasattr(self, "raw_content"): + return set() + mentions = re.findall(r'@{[^;]+; [\w.-]+@[^}]+}', self.raw_content) + if not mentions: + return set() + mentions = {s.split(';')[1].strip(' }') for s in mentions} + mentions = {generate_diaspora_profile_id(s) for s in mentions} + return mentions + @property def id(self): """Diaspora URI scheme format ID. diff --git a/federation/entities/diaspora/mappers.py b/federation/entities/diaspora/mappers.py index 8640601..0889b05 100644 --- a/federation/entities/diaspora/mappers.py +++ b/federation/entities/diaspora/mappers.py @@ -119,6 +119,8 @@ def element_to_objects(element, sender, sender_key_fetcher=None, user=None): "transformed": transformed, }) return [] + # Extract mentions + entity._mentions = entity.extract_mentions() # Do child elements for child in element: entity._children.extend(element_to_objects(child, sender)) diff --git a/federation/tests/entities/diaspora/test_entities.py b/federation/tests/entities/diaspora/test_entities.py index 2d6d733..d28ad7c 100644 --- a/federation/tests/entities/diaspora/test_entities.py +++ b/federation/tests/entities/diaspora/test_entities.py @@ -110,6 +110,22 @@ class TestEntitiesConvertToXML: assert etree.tostring(result).decode("utf-8") == converted +class TestEntitiesExtractMentions: + def test_extract_mentions__empty_set_if_no_raw_content(self, diasporacontact): + assert diasporacontact.extract_mentions() == set() + + def test_extract_mentions__empty_set_if_no_mentions(self, diasporacomment): + assert diasporacomment.extract_mentions() == set() + + def test_extract_mentions__set_contains_mentioned_handles(self, diasporapost): + diasporapost.raw_content = 'yeye @{Jason Robinson 🐍🍻; jaywink@jasonrobinson.me} foobar ' \ + '@{bar; foo@example.com}' + assert diasporapost.extract_mentions() == { + 'diaspora://jaywink@jasonrobinson.me/profile/', + 'diaspora://foo@example.com/profile/', + } + + class TestEntityAttributes: def test_comment_ids(self, diasporacomment): assert diasporacomment.id == "diaspora://handle/comment/guid" @@ -223,7 +239,7 @@ class TestDiasporaRelayableMixin: b'TsLM+Yw==' @patch("federation.entities.diaspora.mappers.DiasporaComment._validate_signatures") - def test_sign_with_parent(self, mock_validate): + def test_sign_with_parent__calls_to_xml(self, mock_validate): entity = DiasporaComment() with patch.object(entity, "to_xml") as mock_to_xml: entity.sign_with_parent(get_dummy_private_key()) diff --git a/federation/tests/entities/diaspora/test_mappers.py b/federation/tests/entities/diaspora/test_mappers.py index 8583cd5..fa02f6c 100644 --- a/federation/tests/entities/diaspora/test_mappers.py +++ b/federation/tests/entities/diaspora/test_mappers.py @@ -17,7 +17,7 @@ from federation.tests.fixtures.payloads import ( DIASPORA_REQUEST, DIASPORA_PROFILE, DIASPORA_POST_INVALID, DIASPORA_RETRACTION, DIASPORA_POST_WITH_PHOTOS, DIASPORA_POST_LEGACY_TIMESTAMP, DIASPORA_POST_LEGACY, DIASPORA_CONTACT, DIASPORA_LEGACY_REQUEST_RETRACTION, DIASPORA_POST_WITH_PHOTOS_2, DIASPORA_PROFILE_EMPTY_TAGS, DIASPORA_RESHARE, - DIASPORA_RESHARE_WITH_EXTRA_PROPERTIES, DIASPORA_RESHARE_LEGACY) + DIASPORA_RESHARE_WITH_EXTRA_PROPERTIES, DIASPORA_RESHARE_LEGACY, DIASPORA_POST_SIMPLE_WITH_MENTION) def mock_fill(attributes): @@ -26,6 +26,12 @@ def mock_fill(attributes): class TestDiasporaEntityMappersReceive: + def test_message_to_objects_mentions_are_extracted(self): + entities = message_to_objects(DIASPORA_POST_SIMPLE_WITH_MENTION, "alice@alice.diaspora.example.org") + assert len(entities) == 1 + post = entities[0] + assert post._mentions == {'diaspora://jaywink@jasonrobinson.me/profile/'} + def test_message_to_objects_simple_post(self): entities = message_to_objects(DIASPORA_POST_SIMPLE, "alice@alice.diaspora.example.org") assert len(entities) == 1 diff --git a/federation/tests/fixtures/payloads.py b/federation/tests/fixtures/payloads.py index e38021b..8e4ef44 100644 --- a/federation/tests/fixtures/payloads.py +++ b/federation/tests/fixtures/payloads.py @@ -73,6 +73,18 @@ DIASPORA_POST_SIMPLE = """ """ +DIASPORA_POST_SIMPLE_WITH_MENTION = """ + + ((status message)) @{Jason Robinson 🐍🍻; jaywink@jasonrobinson.me} + ((guidguidguidguidguidguidguid)) + alice@alice.diaspora.example.org + false + 2011-07-20T01:36:07Z + Socialhome + +""" + + DIASPORA_POST_LEGACY_TIMESTAMP = """ ((status message))