コード例 #1
0
ファイル: inbox.py プロジェクト: sorpaas/microblogpub
def _undo_process_inbox(activity: ap.Undo, new_meta: _NewMeta) -> None:
    _logger.info(f"process_inbox activity={activity!r}")
    # Fetch the object that's been undo'ed
    obj = activity.get_object()

    # Set the undo flag on the mentionned activity
    update_one_activity(by_remote_id(obj.id), upsert({MetaKey.UNDO: True}))

    # Handle cached counters
    if obj.has_type(ap.ActivityType.LIKE):
        # Update the meta counter if the object is published by the server
        update_one_activity(
            {
                **by_object_id(obj.get_object_id()),
                **by_type(ap.ActivityType.CREATE)
            },
            inc(MetaKey.COUNT_LIKE, -1),
        )
    elif obj.has_type(ap.ActivityType.ANNOUNCE):
        announced = obj.get_object()
        # Update the meta counter if the object is published by the server
        update_one_activity(
            {
                **by_type(ap.ActivityType.CREATE),
                **by_object_id(announced.id)
            },
            inc(MetaKey.COUNT_BOOST, -1),
        )
コード例 #2
0
ファイル: outbox.py プロジェクト: btaidm/microblog.pub
def _undo_process_outbox(undo: ap.Undo, new_meta: _NewMeta) -> None:
    _logger.info(f"process_outbox activity={undo!r}")
    obj = undo.get_object()
    update_one_activity({"remote_id": obj.id}, {"$set": {"meta.undo": True}})

    # Undo Like
    if obj.has_type(ap.ActivityType.LIKE):
        liked = obj.get_object_id()
        update_one_activity(
            {
                **by_object_id(liked),
                **by_type(ap.ActivityType.CREATE)
            },
            {
                **inc(MetaKey.COUNT_LIKE, -1),
                **upsert({MetaKey.LIKED: False})
            },
        )

    elif obj.has_type(ap.ActivityType.ANNOUNCE):
        announced = obj.get_object_id()
        update_one_activity(
            {
                **by_object_id(announced),
                **by_type(ap.ActivityType.CREATE)
            },
            upsert({MetaKey.BOOSTED: False}),
        )

    # Undo Follow (undo new following)
    elif obj.has_type(ap.ActivityType.FOLLOW):
        pass
コード例 #3
0
def _announce_process_inbox(announce: ap.Announce, new_meta: _NewMeta) -> None:
    _logger.info(f"process_inbox activity={announce!r}")
    # TODO(tsileo): actually drop it without storing it and better logging, also move the check somewhere else
    # or remove it?
    try:
        obj = announce.get_object()
    except NotAnActivityError:
        _logger.exception(
            f'received an Annouce referencing an OStatus notice ({announce._data["object"]}), dropping the message'
        )
        return

    if obj.has_type(ap.ActivityType.QUESTION):
        Tasks.fetch_remote_question(obj)

    update_one_activity(
        by_remote_id(announce.id),
        upsert({
            MetaKey.OBJECT: obj.to_dict(embed=True),
            MetaKey.OBJECT_ACTOR: obj.get_actor().to_dict(embed=True),
        }),
    )
    update_one_activity(
        {
            **by_type(ap.ActivityType.CREATE),
            **by_object_id(obj.id)
        },
        inc(MetaKey.COUNT_BOOST, 1),
    )
コード例 #4
0
ファイル: inbox.py プロジェクト: sorpaas/microblogpub
def _like_process_inbox(like: ap.Like, new_meta: _NewMeta) -> None:
    _logger.info(f"process_inbox activity={like!r}")
    obj = like.get_object()
    # Update the meta counter if the object is published by the server
    update_one_activity(
        {
            **by_type(ap.ActivityType.CREATE),
            **by_object_id(obj.id)
        },
        inc(MetaKey.COUNT_LIKE, 1),
    )
コード例 #5
0
ファイル: inbox.py プロジェクト: sorpaas/microblogpub
def _delete_process_inbox(delete: ap.Delete, new_meta: _NewMeta) -> None:
    _logger.info(f"process_inbox activity={delete!r}")
    obj_id = delete.get_object_id()
    _logger.debug(f"delete object={obj_id}")
    try:
        # FIXME(tsileo): call the DB here instead? like for the oubox
        obj = ap.fetch_remote_activity(obj_id)
        _logger.info(f"inbox_delete handle_replies obj={obj!r}")
        in_reply_to = obj.get_in_reply_to() if obj.inReplyTo else None
        if obj.has_type(ap.CREATE_TYPES):
            post_query = {
                **by_object_id(obj_id),
                **by_type(ap.ActivityType.CREATE)
            }
            in_reply_to = ap._get_id(
                DB.activities.find_one(post_query)["activity"]["object"].get(
                    "inReplyTo"))
            if in_reply_to:
                DB.activities.update_one(
                    {
                        **by_object_id(in_reply_to),
                        **by_type(ap.ActivityType.CREATE)
                    },
                    inc(MetaKey.COUNT_REPLY, -1),
                )
                DB.replies.update_one(by_remote_id(in_reply_to),
                                      inc(MetaKey.COUNT_REPLY, -1))
    except Exception:
        _logger.exception(f"failed to handle delete replies for {obj_id}")

    update_one_activity(
        {
            **by_object_id(obj_id),
            **by_type(ap.ActivityType.CREATE)
        },
        upsert({MetaKey.DELETED: True}),
    )

    # Foce undo other related activities
    DB.activities.update(by_object_id(obj_id), upsert({MetaKey.UNDO: True}))
コード例 #6
0
ファイル: outbox.py プロジェクト: sorpaas/microblogpub
def _like_process_outbox(like: ap.Like, new_meta: _NewMeta) -> None:
    _logger.info(f"process_outbox activity={like!r}")

    obj = like.get_object()
    if obj.has_type(ap.ActivityType.QUESTION):
        Tasks.fetch_remote_question(obj)

    # Cache the object for display on the "Liked" public page
    Tasks.cache_object(like.id)

    update_one_activity(
        {**by_object_id(obj.id), **by_type(ap.ActivityType.CREATE)},
        {**inc(MetaKey.COUNT_LIKE, 1), **upsert({MetaKey.LIKED: like.id})},
    )
コード例 #7
0
ファイル: inbox.py プロジェクト: sorpaas/microblogpub
def _announce_process_inbox(announce: ap.Announce, new_meta: _NewMeta) -> None:
    _logger.info(f"process_inbox activity={announce!r}")
    # TODO(tsileo): actually drop it without storing it and better logging, also move the check somewhere else
    # or remove it?
    try:
        obj = announce.get_object()
    except NotAnActivityError:
        _logger.exception(
            f'received an Annouce referencing an OStatus notice ({announce._data["object"]}), dropping the message'
        )
        return

    if obj.has_type(ap.ActivityType.QUESTION):
        Tasks.fetch_remote_question(obj)

    # Cache the announced object
    Tasks.cache_object(announce.id)

    # Process the reply of the announced object if any
    in_reply_to = obj.get_in_reply_to()
    if in_reply_to:
        reply = ap.fetch_remote_activity(in_reply_to)
        if reply.has_type(ap.ActivityType.CREATE):
            reply = reply.get_object()

        in_reply_to_data = {MetaKey.IN_REPLY_TO: in_reply_to}
        # Update the activity to save some data about the reply
        if reply.get_actor().id == obj.get_actor().id:
            in_reply_to_data.update({MetaKey.IN_REPLY_TO_SELF: True})
        else:
            in_reply_to_data.update({
                MetaKey.IN_REPLY_TO_ACTOR:
                reply.get_actor().to_dict(embed=True)
            })
        update_one_activity(by_remote_id(announce.id),
                            upsert(in_reply_to_data))
        # Spawn a task to process it (and determine if it needs to be saved)
        Tasks.process_reply(reply.id)

    update_one_activity(
        {
            **by_type(ap.ActivityType.CREATE),
            **by_object_id(obj.id)
        },
        inc(MetaKey.COUNT_BOOST, 1),
    )
コード例 #8
0
def handle_replies(create: ap.Create) -> None:
    """Go up to the root reply, store unknown replies in the `threads` DB and set the "meta.thread_root_parent"
    key to make it easy to query a whole thread."""
    in_reply_to = create.get_object().get_in_reply_to()
    if not in_reply_to:
        return

    reply = ap.fetch_remote_activity(in_reply_to)
    if reply.has_type(ap.ActivityType.CREATE):
        reply = reply.get_object()
    # FIXME(tsileo): can be a 403 too, in this case what to do? not error at least

    # Ensure the this is a local reply, of a question, with a direct "to" addressing
    if (
        reply.id.startswith(BASE_URL)
        and reply.has_type(ap.ActivityType.QUESTION.value)
        and _is_local_reply(create)
        and not create.is_public()
    ):
        return handle_question_reply(create, reply)
    elif (
        create.id.startswith(BASE_URL)
        and reply.has_type(ap.ActivityType.QUESTION.value)
        and not create.is_public()
    ):
        # Keep track of our own votes
        DB.activities.update_one(
            {"activity.object.id": reply.id, "box": "inbox"},
            {
                "$set": {
                    f"meta.poll_answers_sent.{_answer_key(create.get_object().name)}": True
                }
            },
        )
        # Mark our reply as a poll answers, to "hide" it from the UI
        update_one_activity(
            by_remote_id(create.id),
            upsert({MetaKey.POLL_ANSWER: True, MetaKey.POLL_ANSWER_TO: reply.id}),
        )
        return None

    in_reply_to_data = {MetaKey.IN_REPLY_TO: in_reply_to}
    # Update the activity to save some data about the reply
    if reply.get_actor().id == create.get_actor().id:
        in_reply_to_data.update({MetaKey.IN_REPLY_TO_SELF: True})
    else:
        in_reply_to_data.update(
            {MetaKey.IN_REPLY_TO_ACTOR: reply.get_actor().to_dict(embed=True)}
        )
    update_one_activity(by_remote_id(create.id), upsert(in_reply_to_data))

    # It's a regular reply, try to increment the reply counter
    creply = DB.activities.find_one_and_update(
        {**by_object_id(in_reply_to), **by_type(ap.ActivityType.CREATE)},
        inc(MetaKey.COUNT_REPLY, 1),
    )
    if not creply:
        # Maybe it's the reply of a reply?
        DB.replies.find_one_and_update(
            by_remote_id(in_reply_to), inc(MetaKey.COUNT_REPLY, 1)
        )

    # Spawn a task to process it (and determine if it needs to be saved)
    Tasks.process_reply(create.get_object_id())
コード例 #9
0
def task_process_reply() -> _Response:
    """Process `Announce`d posts from Pleroma relays in order to process replies of activities that are in the inbox."""
    task = p.parse(flask.request)
    app.logger.info(f"task={task!r}")
    iri = task.payload
    try:
        activity = ap.fetch_remote_activity(iri)
        app.logger.info(f"checking for reply activity={activity!r}")

        # Some AP server always return Create when requesting an object
        if activity.has_type(ap.ActivityType.CREATE):
            activity = activity.get_object()

        in_reply_to = activity.get_in_reply_to()
        if not in_reply_to:
            # If it's not reply, we can drop it
            app.logger.info(
                f"activity={activity!r} is not a reply, dropping it")
            return ""

        root_reply = in_reply_to

        # Fetch the activity reply
        reply = ap.fetch_remote_activity(in_reply_to)
        if reply.has_type(ap.ActivityType.CREATE):
            reply = reply.get_object()

        new_replies = [activity, reply]

        while 1:
            in_reply_to = reply.get_in_reply_to()
            if not in_reply_to:
                break

            root_reply = in_reply_to
            reply = ap.fetch_remote_activity(root_reply)

            if reply.has_type(ap.ActivityType.CREATE):
                reply = reply.get_object()

            new_replies.append(reply)

        app.logger.info(f"root_reply={reply!r} for activity={activity!r}")

        # In case the activity was from the inbox
        update_one_activity(
            {
                **by_object_id(activity.id),
                **by_type(ap.ActivityType.CREATE)
            },
            upsert({MetaKey.THREAD_ROOT_PARENT: root_reply}),
        )

        for (new_reply_idx, new_reply) in enumerate(new_replies):
            if find_one_activity({
                    **by_object_id(new_reply.id),
                    **by_type(ap.ActivityType.CREATE)
            }) or DB.replies.find_one(by_remote_id(new_reply.id)):
                continue

            actor = new_reply.get_actor()
            is_root_reply = new_reply_idx == len(new_replies) - 1
            if is_root_reply:
                reply_flags: Dict[str, Any] = {}
            else:
                reply_actor = new_replies[new_reply_idx + 1].get_actor()
                is_in_reply_to_self = actor.id == reply_actor.id
                reply_flags = {
                    MetaKey.IN_REPLY_TO_SELF.value: is_in_reply_to_self,
                    MetaKey.IN_REPLY_TO.value: new_reply.get_in_reply_to(),
                }
                if not is_in_reply_to_self:
                    reply_flags[MetaKey.IN_REPLY_TO_ACTOR.
                                value] = reply_actor.to_dict(embed=True)

            # Save the reply with the cached actor and the thread flag/ID
            save_reply(
                new_reply,
                {
                    **reply_flags,
                    MetaKey.THREAD_ROOT_PARENT.value: root_reply,
                    MetaKey.ACTOR.value: actor.to_dict(embed=True),
                    MetaKey.ACTOR_HASH.value: _actor_hash(actor),
                },
            )

            # Update the reply counters
            if new_reply.get_in_reply_to():
                update_one_activity(
                    {
                        **by_object_id(new_reply.get_in_reply_to()),
                        **by_type(ap.ActivityType.CREATE),
                    },
                    inc(MetaKey.COUNT_REPLY, 1),
                )
                DB.replies.update_one(
                    by_remote_id(new_reply.get_in_reply_to()),
                    inc(MetaKey.COUNT_REPLY, 1),
                )

            # Cache the actor icon
            _cache_actor_icon(actor)
            # And cache the attachments
            Tasks.cache_attachments(new_reply.id)
    except (ActivityGoneError, ActivityNotFoundError):
        app.logger.exception(f"dropping activity {iri}, skip processing")
        return ""
    except Exception as err:
        app.logger.exception(f"failed to process new activity {iri}")
        raise TaskError() from err

    return ""