Ejemplo n.º 1
0
def save(box: Box, activity: ap.BaseActivity) -> None:
    """Custom helper for saving an activity to the DB."""
    # Set some "type"-related neta
    meta = _meta(activity)
    if box == Box.OUTBOX and activity.has_type(ap.ActivityType.FOLLOW):
        meta[MetaKey.FOLLOW_STATUS.value] = FollowStatus.WAITING.value
    elif activity.has_type(ap.ActivityType.CREATE):
        mentions = []
        obj = activity.get_object()
        for m in obj.get_mentions():
            mentions.append(m.href)
        hashtags = []
        for h in obj.get_hashtags():
            hashtags.append(h.name[1:])  # Strip the #
        meta.update(
            {MetaKey.MENTIONS.value: mentions, MetaKey.HASHTAGS.value: hashtags}
        )

    DB.activities.insert_one(
        {
            "box": box.value,
            "activity": activity.to_dict(),
            "type": _to_list(activity.type),
            "remote_id": activity.id,
            "meta": meta,
        }
    )
Ejemplo n.º 2
0
def _meta(activity: ap.BaseActivity) -> _NewMeta:
    visibility = ap.get_visibility(activity)
    is_public = False
    if visibility in [ap.Visibility.PUBLIC, ap.Visibility.UNLISTED]:
        is_public = True

    object_id = None
    try:
        object_id = activity.get_object_id()
    except Exception:  # TODO(tsileo): should be ValueError, but replies trigger a KeyError on object
        pass

    object_visibility = None
    if activity.has_type(
        [ap.ActivityType.CREATE, ap.ActivityType.ANNOUNCE, ap.ActivityType.LIKE]
    ):
        object_visibility = ap.get_visibility(activity.get_object()).name

    actor_id = activity.get_actor().id

    return {
        MetaKey.UNDO.value: False,
        MetaKey.DELETED.value: False,
        MetaKey.PUBLIC.value: is_public,
        MetaKey.SERVER.value: urlparse(activity.id).netloc,
        MetaKey.VISIBILITY.value: visibility.name,
        MetaKey.ACTOR_ID.value: actor_id,
        MetaKey.OBJECT_ID.value: object_id,
        MetaKey.OBJECT_VISIBILITY.value: object_visibility,
        MetaKey.POLL_ANSWER.value: False,
        MetaKey.PUBLISHED.value: activity.published if activity.published else now(),
    }
Ejemplo n.º 3
0
def update_cached_actor(actor: ap.BaseActivity) -> None:
    actor_hash = _actor_hash(actor)
    update_many_activities(
        {
            **flag(MetaKey.ACTOR_ID, actor.id),
            **flag(MetaKey.ACTOR_HASH, {"$ne": actor_hash}),
        },
        upsert({
            MetaKey.ACTOR: actor.to_dict(embed=True),
            MetaKey.ACTOR_HASH: actor_hash
        }),
    )
    update_many_activities(
        {
            **flag(MetaKey.OBJECT_ACTOR_ID, actor.id),
            **flag(MetaKey.OBJECT_ACTOR_HASH, {"$ne": actor_hash}),
        },
        upsert({
            MetaKey.OBJECT_ACTOR: actor.to_dict(embed=True),
            MetaKey.OBJECT_ACTOR_HASH: actor_hash,
        }),
    )
    # TODO(tsileo): Also update following (it's in the object)
    # DB.activities.update_many(
    #     {"meta.object_id": actor.id}, {"$set": {"meta.object": actor.to_dict(embed=True)}}
    # )
    _cache_actor_icon(actor)
Ejemplo n.º 4
0
def post_to_outbox(activity: ap.BaseActivity) -> str:
    if activity.has_type(ap.CREATE_TYPES):
        activity = activity.build_create()

    # Assign create a random ID
    obj_id = back.random_object_id()
    activity.set_id(back.activity_url(obj_id), obj_id)

    back.save(Box.OUTBOX, activity)
    cache_actor.delay(activity.id)
    finish_post_to_outbox.delay(activity.id)
    return activity.id
Ejemplo n.º 5
0
 def outbox_new(self, as_actor: ap.Person,
                activity: ap.BaseActivity) -> None:
     print(f"saving {activity!r} to DB")
     actor_id = activity.get_actor().id
     if activity.id in self.OUTBOX_IDX[actor_id]:
         return
     self.DB[actor_id]["outbox"].append(activity)
     self.OUTBOX_IDX[actor_id][activity.id] = activity
     self.FETCH_MOCK[activity.id] = activity.to_dict()
     if isinstance(activity, ap.Create):
         self.FETCH_MOCK[
             activity.get_object().id] = activity.get_object().to_dict()
Ejemplo n.º 6
0
def update_remote_actor(actor_id: int,
                        activity_actor: ap.BaseActivity) -> None:
    """
    :param actor_id: an Actor db ID
    :param activity_actor: a Little Boxes Actor object
    :return: nothing
    """
    actor = Actor.query.filter(Actor.id == actor_id).first()
    current_app.logger.debug(
        f"asked to update Actor {actor_id}: {activity_actor!r}")

    actor.preferred_username = activity_actor.preferredUsername
    domain = urlparse(activity_actor.url)
    actor.domain = domain.netloc
    actor.name = activity_actor.name
    actor.manually_approves_followers = False
    actor.url = activity_actor.id  # FIXME: or .id ??? [cf backend.py:52-53]
    actor.shared_inbox_url = activity_actor._data.get("endpoints",
                                                      {}).get("sharedInbox")
    actor.inbox_url = activity_actor.inbox
    actor.outbox_url = activity_actor.outbox
    actor.public_key = activity_actor.get_key().pubkey_pem
    actor.summary = activity_actor.summary
    actor.followers_url = activity_actor.followers
    actor.following_url = activity_actor.following

    db.session.commit()
Ejemplo n.º 7
0
 def cache_emojis(activity: ap.BaseActivity) -> None:
     for emoji in activity.get_emojis():
         try:
             Tasks.cache_emoji(emoji.get_icon_url(), emoji.id)
         except KeyError:
             # TODO(tsileo): log invalid emoji
             pass
Ejemplo n.º 8
0
def post_to_outbox(activity: ap.BaseActivity) -> str:
    current_app.logger.debug(f"post_to_outbox {activity}")

    if activity.has_type(ap.CREATE_TYPES):
        activity = activity.build_create()

    backend = ap.get_backend()

    # Assign a random ID
    obj_id = backend.random_object_id()
    activity.set_id(backend.activity_url(obj_id), obj_id)

    backend.save(Box.OUTBOX, activity)

    finish_post_to_outbox.delay(activity.id)
    return activity.id
Ejemplo n.º 9
0
def create_remote_actor(activity_actor: ap.BaseActivity):
    """
    :param activity_actor: a Little Boxes Actor object
    :return: an Actor object
    """
    actor = Actor()
    print(activity_actor)
    actor.preferred_username = activity_actor.preferredUsername
    domain = urlparse(activity_actor.url)
    actor.domain = domain.netloc
    actor.type = "Person"
    # FIXME: test for .name, it won't exist if not set (at least for mastodon)
    actor.name = activity_actor.preferredUsername  # mastodon don't have .name
    actor.manually_approves_followers = False
    actor.url = activity_actor.id  # FIXME: or .id ??? [cf backend.py:52-53]
    actor.shared_inbox_url = activity_actor._data.get("endpoints",
                                                      {}).get("sharedInbox")
    actor.inbox_url = activity_actor.inbox
    actor.outbox_url = activity_actor.outbox
    actor.public_key = activity_actor.get_key().pubkey_pem
    actor.summary = activity_actor.summary
    actor.followers_url = activity_actor.followers
    actor.following_url = activity_actor.following

    return actor
Ejemplo n.º 10
0
def create_remote_actor(activity_actor: ap.BaseActivity):
    """
    :param activity_actor: a Little Boxes Actor object
    :return: an Actor object
    """
    actor = Actor()
    actor.preferred_username = activity_actor.preferredUsername
    domain = urlparse(activity_actor.url)
    actor.domain = domain.netloc
    actor.type = "Person"
    actor.name = activity_actor.name
    actor.manually_approves_followers = False
    actor.url = activity_actor.id  # FIXME: or .id ??? [cf backend.py:52-53]
    actor.shared_inbox_url = activity_actor._data.get("endpoints", {}).get("sharedInbox")
    actor.inbox_url = activity_actor.inbox
    actor.outbox_url = activity_actor.outbox
    actor.public_key = activity_actor.get_key().pubkey_pem
    actor.summary = activity_actor.summary
    actor.followers_url = activity_actor.followers
    actor.following_url = activity_actor.following

    user = User()
    user.email = None
    user.name = activity_actor.preferredUsername
    user.password = None
    user.active = False
    user.confirmed_at = None
    user.display_name = activity_actor.name
    user.local = False

    actor.user = user

    # TODO: Avatar

    return actor, user
Ejemplo n.º 11
0
    def save(self, box: Box, activity: ap.BaseActivity) -> None:
        """Save an Activity in database"""

        current_app.logger.info(f"asked to save an activity {activity!r}")

        # Save remote Actor
        ap_actor = activity.get_actor()
        domain = urlparse(ap_actor.id)
        current_app.logger.debug(f"actor.id=={ap_actor.__dict__}")

        current_app.logger.debug(f"actor domain {domain.netloc} and " f"name {ap_actor.preferredUsername}")

        actor = Actor.query.filter(Actor.domain == domain.netloc, Actor.name == ap_actor.preferredUsername).first()

        # FIXME TODO: check if it still works with unknown remote actor
        if not actor:
            current_app.logger.debug("cannot find actor")
            actor = Actor.query.filter(Actor.url == ap_actor.id).first()
            if not actor:
                current_app.logger.debug(f"actor {ap_actor.id} not found")
                actor, user = create_remote_actor(ap_actor)
                db.session.add(user)
                db.session.add(actor)
                current_app.logger.debug("created one in DB")
            else:
                current_app.logger.debug(f"got local one {actor.url}")
        else:
            current_app.logger.debug(f"got remote one {actor.url}")

        # Save Activity
        act = Activity()
        act.payload = activity.to_dict()
        act.url = activity.id
        act.type = activity.type
        act.box = box.value

        # Activity is local only if the url starts like BASE_URL
        base_url = current_app.config["BASE_URL"]
        act.local = activity.id.startswith(base_url)

        act.actor_id = actor.id

        db.session.add(act)

        db.session.commit()
Ejemplo n.º 12
0
    def save(self, box: Box, activity: ap.BaseActivity) -> None:
        """Custom helper for saving an activity to the DB."""
        is_public = True
        if activity.has_type(
                ap.ActivityType.CREATE) and not activity.is_public():
            is_public = False

        DB.activities.insert_one({
            "box": box.value,
            "activity": activity.to_dict(),
            "type": _to_list(activity.type),
            "remote_id": activity.id,
            "meta": {
                "undo": False,
                "deleted": False,
                "public": is_public
            },
        })
Ejemplo n.º 13
0
def post_to_outbox(activity: ap.BaseActivity) -> str:
    current_app.logger.debug(f"post_to_outbox {activity!r}")

    if activity.has_type(ap.CREATE_TYPES):
        print("BUILD CREATE POST TO OUTBOX")
        activity = activity.build_create()

    backend = ap.get_backend()

    # Assign a random ID
    obj_id = backend.random_object_id()
    activity.set_id(backend.activity_url(obj_id), obj_id)

    backend.save(Box.OUTBOX, activity)

    # Broadcast only if AP is enabled
    if current_app.config["AP_ENABLED"]:
        finish_post_to_outbox.delay(activity.id)
    return activity.id
Ejemplo n.º 14
0
 def outbox_delete(self, as_actor: ap.Person, activity: ap.BaseActivity) -> None:
     current_app.logger.debug(f"outbox_delete {activity!r} as {as_actor!r}")
     # Fetch linked activity and mark it deleted
     # Somehow we needs to remove /activity here
     # FIXME do that better
     activity_uri = activity.get_object_id().rstrip("/activity")
     current_app.logger.debug(f"id: {activity_uri}")
     orig_activity = Activity.query.filter(Activity.url == activity_uri, Activity.type == "Create").first()
     orig_activity.meta_deleted = True
     db.session.commit()
Ejemplo n.º 15
0
def accept_follow(activity: ap.BaseActivity) -> str:
    actor_id = activity.get_actor().id
    accept = ap.Accept(
        actor=ID,
        context=new_context(activity),
        object={
            "type": "Follow",
            "id": activity.id,
            "object": activity.get_object_id(),
            "actor": actor_id,
        },
        to=[actor_id],
        published=now(),
    )
    update_one_activity(
        by_remote_id(activity.id),
        upsert({MetaKey.FOLLOW_STATUS: FollowStatus.ACCEPTED.value}),
    )
    return post_to_outbox(accept)
Ejemplo n.º 16
0
 def save(self, box: Box, activity: ap.BaseActivity) -> None:
     """Custom helper for saving an activity to the DB."""
     DB.activities.insert_one(
         {
             "box": box.value,
             "activity": activity.to_dict(),
             "type": _to_list(activity.type),
             "remote_id": activity.id,
             "meta": {"undo": False, "deleted": False},
         }
     )
Ejemplo n.º 17
0
    def new_following(self, activity: ap.BaseActivity, obj: ap.BaseActivity) -> None:
        current_app.logger.info("new following")

        ap_from = obj.get_actor()  # Who initiated the follow
        ap_to = activity.get_actor()  # who to be followed

        db_from = Actor.query.filter(Actor.url == ap_from.id).first()
        db_to = Actor.query.filter(Actor.url == ap_to.id).first()
        if not db_from:
            current_app.logger.error(f"cannot find actor {ap_from!r}")
            return
        if not db_to:
            current_app.logger.error(f"cannot find follow {ap_to!r}")
            return

        current_app.logger.info(f"{db_from.name} wanted to follow {db_to.name}")

        # FIXME: may be the reverse, db_follow follow db_actor
        db_from.follow(activity.id, db_to)
        db.session.commit()
        current_app.logger.info("new following saved")
Ejemplo n.º 18
0
def save_reply(activity: ap.BaseActivity, meta: Dict[str, Any] = {}) -> None:
    visibility = ap.get_visibility(activity)
    is_public = False
    if visibility in [ap.Visibility.PUBLIC, ap.Visibility.UNLISTED]:
        is_public = True

    published = activity.published if activity.published else now()
    DB.replies.insert_one({
        "activity": activity.to_dict(),
        "type": _to_list(activity.type),
        "remote_id": activity.id,
        "meta": {
            "undo": False,
            "deleted": False,
            "public": is_public,
            "server": urlparse(activity.id).netloc,
            "visibility": visibility.name,
            "actor_id": activity.get_actor().id,
            MetaKey.PUBLISHED.value: published,
            **meta,
        },
    })
Ejemplo n.º 19
0
def save(box: Box, activity: ap.BaseActivity) -> None:
    """Custom helper for saving an activity to the DB."""
    visibility = ap.get_visibility(activity)
    is_public = False
    if visibility in [ap.Visibility.PUBLIC, ap.Visibility.UNLISTED]:
        is_public = True

    object_id = None
    try:
        object_id = activity.get_object_id()
    except Exception:  # TODO(tsileo): should be ValueError, but replies trigger a KeyError on object
        pass

    object_visibility = None
    if activity.has_type([
            ap.ActivityType.CREATE, ap.ActivityType.ANNOUNCE,
            ap.ActivityType.LIKE
    ]):
        object_visibility = ap.get_visibility(activity.get_object()).name

    actor_id = activity.get_actor().id

    DB.activities.insert_one({
        "box": box.value,
        "activity": activity.to_dict(),
        "type": _to_list(activity.type),
        "remote_id": activity.id,
        "meta": {
            "undo": False,
            "deleted": False,
            "public": is_public,
            "server": urlparse(activity.id).netloc,
            "visibility": visibility.name,
            "actor_id": actor_id,
            "object_id": object_id,
            "object_visibility": object_visibility,
            "poll_answer": False,
        },
    })
Ejemplo n.º 20
0
def post_to_inbox(activity: ap.BaseActivity) -> None:
    # Check for Block activity
    actor = activity.get_actor()
    if outbox_is_blocked(actor.id):
        logger.info(
            f"actor {actor!r} is blocked, dropping the received activity {activity!r}"
        )
        return

    if DB.activities.find_one({
            "box": Box.INBOX.value,
            "remote_id": activity.id
    }):
        # The activity is already in the inbox
        logger.info(f"received duplicate activity {activity!r}, dropping it")
        return

    save(Box.INBOX, activity)
    logger.info(f"spawning tasks for {activity!r}")
    if not activity.has_type(ap.ActivityType.DELETE):
        Tasks.cache_actor(activity.id)
    Tasks.process_new_activity(activity.id)
    Tasks.finish_post_to_inbox(activity.id)
Ejemplo n.º 21
0
def post_to_outbox(activity: ap.BaseActivity) -> str:
    if activity.has_type(ap.CREATE_TYPES):
        activity = activity.build_create()

    # Assign create a random ID
    obj_id = binascii.hexlify(os.urandom(8)).decode("utf-8")
    uri = activity_url(obj_id)
    activity._data["id"] = uri
    if activity.has_type(ap.ActivityType.CREATE):
        activity._data["object"]["id"] = urljoin(
            BASE_URL, url_for("outbox_activity", item_id=obj_id))
        activity._data["object"]["url"] = urljoin(
            BASE_URL, url_for("note_by_id", note_id=obj_id))
        activity.reset_object_cache()

    save(Box.OUTBOX, activity)
    Tasks.cache_actor(activity.id)
    Tasks.finish_post_to_outbox(activity.id)
    return activity.id
Ejemplo n.º 22
0
def post_to_inbox(activity: ap.BaseActivity) -> None:
    # Check for Block activity
    actor = activity.get_actor()
    if back.outbox_is_blocked(MY_PERSON, actor.id):
        log.info(
            f"actor {actor!r} is blocked, dropping the received activity {activity!r}"
        )
        return

    if back.inbox_check_duplicate(MY_PERSON, activity.id):
        # The activity is already in the inbox
        log.info(f"received duplicate activity {activity!r}, dropping it")

    back.save(Box.INBOX, activity)
    process_new_activity.delay(activity.id)

    log.info(f"spawning task for {activity!r}")
    finish_post_to_inbox.delay(activity.id)
Ejemplo n.º 23
0
def create_remote_actor(activity_actor: ap.BaseActivity):
    """
    :param activity_actor: a Little Boxes Actor object
    :return: an Actor object
    """
    actor = Actor()
    actor.preferred_username = activity_actor.preferredUsername
    # TODO this should be handled better, like with pyld...
    # handle funkwhale Actor types
    url = activity_actor.url
    if isinstance(url, list):
        url = next(
            iter(item for item in url if item["type"] == "Link"
                 and item["mediaType"] == "text/html"), None)["href"]
    domain = urlparse(url)
    actor.domain = domain.netloc
    actor.type = "Person"
    actor.name = activity_actor.name
    actor.manually_approves_followers = False
    actor.url = activity_actor.id  # FIXME: or .id ??? [cf backend.py:52-53]
    actor.shared_inbox_url = activity_actor._data.get("endpoints",
                                                      {}).get("sharedInbox")
    actor.inbox_url = activity_actor.inbox
    actor.outbox_url = activity_actor.outbox
    actor.public_key = activity_actor.get_key().pubkey_pem
    actor.summary = activity_actor.summary
    actor.followers_url = activity_actor.followers
    actor.following_url = activity_actor.following

    user = User()
    user.email = None
    user.name = activity_actor.preferredUsername
    user.password = None
    user.active = False
    user.confirmed_at = None
    user.display_name = activity_actor.name
    user.local = False

    actor.user = user

    # TODO: Avatar

    return actor, user
Ejemplo n.º 24
0
    def post_to_outbox(self, activity: ap.BaseActivity) -> None:
        if activity.has_type(ap.CREATE_TYPES):
            activity = activity.build_create()

        self.save(Box.OUTBOX, activity)

        # Assign create a random ID
        obj_id = self.random_object_id()
        activity.set_id(self.activity_url(obj_id), obj_id)

        recipients = activity.recipients()
        logger.info(f"recipients={recipients}")
        activity = ap.clean_activity(activity.to_dict())

        payload = json.dumps(activity)
        for recp in recipients:
            logger.debug(f"posting to {recp}")
            self.post_to_remote_inbox(self.get_actor(), payload, recp)
Ejemplo n.º 25
0
def post_to_inbox(activity: ap.BaseActivity) -> None:
    # Check for Block activity
    actor = activity.get_actor()
    if outbox_is_blocked(actor.id):
        logger.info(
            f"actor {actor!r} is blocked, dropping the received activity {activity!r}"
        )
        return

    # If the message is coming from a Pleroma relay, we process it as a possible reply for a stream activity
    if (
        actor.has_type(ap.ActivityType.APPLICATION)
        and actor.id.endswith("/relay")
        and activity.has_type(ap.ActivityType.ANNOUNCE)
        and not find_one_activity(
            {
                **by_object_id(activity.get_object_id()),
                **by_type(ap.ActivityType.CREATE),
            }
        )
        and not DB.replies.find_one(by_remote_id(activity.get_object_id()))
    ):
        Tasks.process_reply(activity.get_object_id())
        return

    # Hubzilla sends Update with the same ID as the actor, and it poisons the cache
    if (
        activity.has_type(ap.ActivityType.UPDATE)
        and activity.id == activity.get_object_id()
    ):
        # Start a task to update the cached actor
        Tasks.cache_actor(activity.id)
        return

    # Honk forwards activities in a Read, process them as replies
    if activity.has_type(ap.ActivityType.READ):
        Tasks.process_reply(activity.get_object_id())
        return

    # TODO(tsileo): support ignore from Honk

    # Hubzilla forwards activities in a Create, process them as possible replies
    if activity.has_type(ap.ActivityType.CREATE) and server(activity.id) != server(
        activity.get_object_id()
    ):
        Tasks.process_reply(activity.get_object_id())
        return

    if DB.activities.find_one({"box": Box.INBOX.value, "remote_id": activity.id}):
        # The activity is already in the inbox
        logger.info(f"received duplicate activity {activity!r}, dropping it")
        return

    save(Box.INBOX, activity)
    logger.info(f"spawning tasks for {activity!r}")
    if not activity.has_type([ap.ActivityType.DELETE, ap.ActivityType.UPDATE]):
        Tasks.cache_actor(activity.id)
    Tasks.process_new_activity(activity.id)
    Tasks.finish_post_to_inbox(activity.id)