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))