def user_to_actor(self, user):
        """Converts a user to an actor.

    Args:
      user: dict, a decoded JSON Facebook user

    Returns:
      an ActivityStreams actor dict, ready to be JSON-encoded
    """
        if not user:
            return {}

        id = user.get("id")
        username = user.get("username")
        handle = username or id
        if not handle:
            return {}

        url = user.get("link")
        if not url:
            url = "http://facebook.com/" + handle

        # facebook implements this as a 302 redirect
        image_url = "http://graph.facebook.com/%s/picture?type=large" % handle
        actor = {
            "displayName": user.get("name"),
            "image": {"url": image_url},
            "id": self.tag_uri(handle),
            "updated": util.maybe_iso8601_to_rfc3339(user.get("updated_time")),
            "url": url,
            "username": username,
            "description": user.get("bio"),
        }

        location = user.get("location")
        if location:
            actor["location"] = {"id": location.get("id"), "displayName": location.get("name")}

        return util.trim_nulls(actor)
    def post_to_object(self, post):
        """Converts a post to an object.

    Args:
      post: dict, a decoded JSON post

    Returns:
      an ActivityStreams object dict, ready to be JSON-encoded
    """
        object = {}

        id = post.get("id")
        if not id:
            return {}

        post_type = post.get("type")
        status_type = post.get("status_type")
        url = "http://facebook.com/" + id.replace("_", "/posts/")
        picture = post.get("picture")
        message = post.get("message")
        if not message:
            message = post.get("story")
        display_name = None

        data = post.get("data", {})
        for field in ("object", "song"):
            obj = data.get(field)
            if obj:
                id = obj.get("id")
                post_type = obj.get("type")
                url = obj.get("url")
                display_name = obj.get("title")

        object_type = OBJECT_TYPES.get(post_type)
        author = self.user_to_actor(post.get("from"))
        link = post.get("link", "")

        if link.startswith("/gifts/"):
            object_type = "product"
        if not object_type:
            if picture and not message:
                object_type = "image"
            else:
                object_type = "note"

        object = {
            "id": self.tag_uri(str(id)),
            "objectType": object_type,
            "published": util.maybe_iso8601_to_rfc3339(post.get("created_time")),
            "updated": util.maybe_iso8601_to_rfc3339(post.get("updated_time")),
            "author": author,
            "content": message,
            # FB post ids are of the form USERID_POSTID
            "url": url,
            "image": {"url": picture},
            "displayName": display_name,
        }

        # tags
        tags = itertools.chain(
            post.get("to", {}).get("data", []),
            post.get("with_tags", {}).get("data", []),
            *post.get("message_tags", {}).values()
        )
        object["tags"] = [
            {
                "objectType": OBJECT_TYPES.get(t.get("type"), "person"),
                "id": self.tag_uri(t.get("id")),
                "url": "http://facebook.com/%s" % t.get("id"),
                "displayName": t.get("name"),
                "startIndex": t.get("offset"),
                "length": t.get("length"),
            }
            for t in tags
        ]

        # is there an attachment? prefer to represent it as a picture (ie image
        # object), but if not, fall back to a link.
        att = {
            "url": link if link else url,
            "image": {"url": picture},
            "displayName": post.get("name"),
            "summary": post.get("caption"),
            "content": post.get("description"),
        }

        if picture and picture.endswith("_s.jpg") and (post_type == "photo" or status_type == "added_photos"):
            # a picture the user posted. get a larger size.
            att.update({"objectType": "image", "image": {"url": picture[:-6] + "_o.jpg"}})
            object["attachments"] = [att]
        elif link and not link.startswith("/gifts/"):
            att["objectType"] = "article"
            object["attachments"] = [att]

        # location
        place = post.get("place")
        if place:
            id = place.get("id")
            object["location"] = {"displayName": place.get("name"), "id": id, "url": "http://facebook.com/" + id}
            location = place.get("location", None)
            if isinstance(location, dict):
                lat = location.get("latitude")
                lon = location.get("longitude")
                if lat and lon:
                    object["location"].update(
                        {
                            "latitude": lat,
                            "longitude": lon,
                            # ISO 6709 location string. details: http://en.wikipedia.org/wiki/ISO_6709
                            "position": "%+f%+f/" % (lat, lon),
                        }
                    )

        # comments go in the replies field, according to the "Responses for
        # Activity Streams" extension spec:
        # http://activitystrea.ms/specs/json/replies/1.0/
        comments = post.get("comments", {}).get("data")
        if comments:
            items = [self.comment_to_object(c) for c in comments]
            object["replies"] = {"items": items, "totalItems": len(items)}

        return util.trim_nulls(object)