federation/federation/entities/diaspora/mappers.py

144 wiersze
5.3 KiB
Python

# -*- coding: utf-8 -*-
import logging
from datetime import datetime
from lxml import etree
from federation.entities.base import Image, Relationship, Post, Reaction, Comment, Profile, Retraction
from federation.entities.diaspora.entities import (
DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest, DiasporaProfile, DiasporaRetraction)
logger = logging.getLogger("social-federation")
MAPPINGS = {
"status_message": DiasporaPost,
"photo": Image,
"comment": DiasporaComment,
"like": DiasporaLike,
"request": DiasporaRequest,
"profile": DiasporaProfile,
"retraction": DiasporaRetraction,
}
BOOLEAN_KEYS = [
"public",
"nsfw",
]
DATETIME_KEYS = [
"created_at",
]
def xml_children_as_dict(node):
"""Turn the children of node <xml> into a dict, keyed by tag name.
This is only a shallow conversation - child nodes are not recursively processed.
"""
return dict((e.tag, e.text) for e in node)
def message_to_objects(message):
"""Takes in a message extracted by a protocol and maps it to entities."""
doc = etree.fromstring(message)
entities = []
for element in doc.iter():
cls = MAPPINGS.get(element.tag, None)
if cls:
attrs = xml_children_as_dict(element)
transformed = transform_attributes(attrs)
if hasattr(cls, "fill_extra_attributes"):
transformed = cls.fill_extra_attributes(transformed)
entity = cls(**transformed)
try:
entity.validate()
entities.append(entity)
except ValueError as ex:
logger.error("Failed to validate entity %s: %s", entity, ex, extra={
"attrs": attrs,
"transformed": transformed,
})
continue
if cls == DiasporaRequest:
# We support sharing/following separately, so also generate base Relationship for the following part
transformed.update({"relationship": "following"})
entity = Relationship(**transformed)
entities.append(entity)
return entities
def transform_attributes(attrs):
"""Transform some attribute keys."""
transformed = {}
for key, value in attrs.items():
if key in ["raw_message", "text"]:
transformed["raw_content"] = value
elif key in ["diaspora_handle", "sender_handle", "author"]:
transformed["handle"] = value
elif key == "recipient_handle":
transformed["target_handle"] = value
elif key == "parent_guid":
transformed["target_guid"] = value
elif key == "first_name":
transformed["name"] = value
elif key == "image_url":
if "image_urls" not in transformed:
transformed["image_urls"] = {}
transformed["image_urls"]["large"] = value
elif key == "image_url_small":
if "image_urls" not in transformed:
transformed["image_urls"] = {}
transformed["image_urls"]["small"] = value
elif key == "image_url_medium":
if "image_urls" not in transformed:
transformed["image_urls"] = {}
transformed["image_urls"]["medium"] = value
elif key == "tag_string":
transformed["tag_list"] = value.replace("#", "").split(" ")
elif key == "bio":
transformed["raw_content"] = value
elif key == "searchable":
transformed["public"] = True if value == "true" else False
elif key == "target_type":
transformed["entity_type"] = DiasporaRetraction.entity_type_from_remote(value)
elif key in BOOLEAN_KEYS:
transformed[key] = True if value == "true" else False
elif key in DATETIME_KEYS:
transformed[key] = datetime.strptime(value, "%Y-%m-%d %H:%M:%S %Z")
else:
transformed[key] = value or ""
return transformed
def get_outbound_entity(entity):
"""Get the correct outbound entity for this protocol.
We might have to look at entity values to decide the correct outbound entity.
If we cannot find one, we should raise as conversion cannot be guaranteed to the given protocol.
:arg entity: An entity instance which can be of a base or protocol entity class.
:returns: Protocol specific entity class instance.
:raises ValueError: If conversion cannot be done.
"""
cls = entity.__class__
if cls in [DiasporaPost, DiasporaRequest, DiasporaComment, DiasporaLike, DiasporaProfile, DiasporaRetraction]:
# Already fine
return entity
elif cls == Post:
return DiasporaPost.from_base(entity)
elif cls == Comment:
return DiasporaComment.from_base(entity)
elif cls == Reaction:
if entity.reaction == "like":
return DiasporaLike.from_base(entity)
elif cls == Relationship:
if entity.relationship in ["sharing", "following"]:
# Unfortunately we must send out in both cases since in Diaspora they are the same thing
return DiasporaRequest.from_base(entity)
elif cls == Profile:
return DiasporaProfile.from_base(entity)
elif cls == Retraction:
return DiasporaRetraction.from_base(entity)
raise ValueError("Don't know how to convert this base entity to Diaspora protocol entities.")