Exemplo n.º 1
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()
Exemplo n.º 2
0
def search():
    """
    Search.
    ---
    tags:
        - Global
    parameters:
        - name: q
          in: query
          type: string
          required: true
          description: search string
    responses:
        200:
            description: fixme.
    """
    # Get logged in user from bearer token, or None if not logged in
    if current_token:
        current_user = current_token.user
    else:
        current_user = None

    s = request.args.get("q", None)
    if not s:
        return jsonify({"error": "No search string provided"}), 400

    # This is the old search endpoint and needs to be improved
    # Especially tracks and accounts needs to be returned in the right format, with the data helpers
    # Users should be searched from known Actors or fetched
    # URI should be searched from known activities or fetched
    # FTS, well, FTS needs to be implemented

    results = {"accounts": [], "sounds": [], "mode": None, "from": None}

    if current_user:
        results["from"] = current_user.name

    # Search for sounds
    # TODO: Implement FTS to get sounds search
    sounds = []

    # Search for accounts
    accounts = []
    is_user_at_account = RE_ACCOUNT.match(s)

    if s.startswith("https://"):
        # Try to match the URI from Activities in database
        results["mode"] = "uri"
        users = Actor.query.filter(Actor.meta_deleted.is_(False),
                                   Actor.url == s).all()
    elif is_user_at_account:
        # It matches [email protected], try to match it locally
        results["mode"] = "acct"
        user = is_user_at_account.group("user")
        instance = is_user_at_account.group("instance")
        users = Actor.query.filter(Actor.meta_deleted.is_(False),
                                   Actor.preferred_username == user,
                                   Actor.domain == instance).all()
    else:
        # It's a FTS search
        results["mode"] = "username"
        # Match actor username in database
        if current_user:
            users = (db.session.query(Actor, Follower).outerjoin(
                Follower,
                and_(Actor.id == Follower.target_id,
                     Follower.actor_id == current_user.actor[0].id)).filter(
                         or_(Actor.preferred_username.contains(s),
                             Actor.name.contains(s))).filter(
                                 not_(Actor.id ==
                                      current_user.actor[0].id)).all())
        else:
            users = (db.session.query(Actor).filter(
                or_(Actor.preferred_username.contains(s),
                    Actor.name.contains(s))).all())

    # Handle the found users
    if len(users) > 0:
        for actor in users:
            relationship = False
            if current_user:
                relationship = to_json_relationship(current_user, actor.user)
            accounts.append(to_json_account(actor.user, relationship))

    if len(accounts) <= 0:
        # Do a webfinger
        # TODO FIXME: We should do this only if https:// or user@account submitted
        # And rework it slightly differently since we needs to backend.fetch_iri() for https:// who
        # can match a Sound and not only an Actor
        current_app.logger.debug(f"webfinger for {s}")
        try:
            remote_actor_url = get_actor_url(s, debug=current_app.debug)
            # We need to get the remote Actor
            backend = ap.get_backend()
            iri = backend.fetch_iri(remote_actor_url)
            if iri:
                # We have fetched an unknown Actor
                # Save it in database and return it properly
                current_app.logger.debug(
                    f"got remote actor URL {remote_actor_url}")

                act = ap.parse_activity(iri)

                fetched_actor, fetched_user = create_remote_actor(act)
                db.session.add(fetched_user)
                db.session.add(fetched_actor)
                db.session.commit()

                relationship = False
                if current_user:
                    relationship = to_json_relationship(
                        current_user, fetched_user)
                accounts.append(to_json_account(fetched_user, relationship))
                results["mode"] = "webfinger"

        except (InvalidURLError, ValueError):
            current_app.logger.exception(f"Invalid AP URL: {s}")
            # Then test fetching as a "normal" Activity ?
    # Finally fill the results dict
    results["accounts"] = accounts

    # FIXME: handle exceptions
    if results["mode"] == "uri" and len(sounds) <= 0:
        backend = ap.get_backend()
        iri = backend.fetch_iri(s)
        if iri:
            # FIXME: Is INBOX the right choice here ?
            backend.save(Box.INBOX, iri)
        # Fetch again, but get it from database
        activity = Activity.query.filter(Activity.url == iri).first()
        if not activity:
            current_app.logger.exception("WTF Activity is not saved")
        else:
            from tasks import create_sound_for_remote_track, upload_workflow

            sound_id = create_sound_for_remote_track(activity)
            sound = Sound.query.filter(Sound.id == sound_id).one()
            upload_workflow.delay(sound.id)

            relationship = False
            if current_user:
                relationship = to_json_relationship(current_user, sound.user)
            acct = to_json_account(sound.user, relationship)
            sounds.append(to_json_track(sound, acct))

    return jsonify({"who": s, "results": results})
Exemplo n.º 3
0
def unfollow():
    user = request.args.get("user")

    actor_me = current_user.actor[0]

    if user.startswith("https://"):
        actor = Actor.query.filter(Actor.url == user).first()
        local_user = actor.user
    else:
        local_user = User.query.filter(User.name == user).first()

    if local_user:
        # Process local unfollow
        actor_me.unfollow(local_user.actor[0])
        flash(gettext("Unfollow successful"), "success")
    else:
        # Might be a remote unfollow

        # 1. Webfinger the user
        try:
            remote_actor_url = get_actor_url(user, debug=current_app.debug)
        except InvalidURLError:
            current_app.logger.exception(f"Invalid webfinger URL: {user}")
            remote_actor_url = None
        except requests.exceptions.HTTPError:
            current_app.logger.exception(f"Invali webfinger URL: {user}")
            remote_actor_url = None

        if not remote_actor_url:
            flash(gettext("User not found"), "error")
            return redirect(url_for("bp_users.profile",
                                    name=current_user.name))

        # 2. Check if we have a local user
        actor_target = Actor.query.filter(
            Actor.url == remote_actor_url).first()

        if not actor_target:
            # 2.5 Fetch and save remote actor
            backend = ap.get_backend()
            iri = backend.fetch_iri(remote_actor_url)
            if not iri:
                flash(gettext("User not found"), "error")
                return redirect(url_for("bp_main.home"))
            act = ap.parse_activity(iri)
            actor_target, user_target = create_remote_actor(act)
            db.session.add(user_target)
            db.session.add(actor_target)

        # 2.5 Get the relation of the follow
        follow_relation = Follower.query.filter(
            Follower.actor_id == actor_me.id,
            Follower.target_id == actor_target.id).first()
        if not follow_relation:
            flash(gettext("You don't follow this user"), "error")
            return redirect(url_for("bp_users.profile",
                                    name=current_user.name))

        # 3. Fetch the Activity of the Follow
        accept_activity = Activity.query.filter(
            Activity.url == follow_relation.activity_url).first()
        if not accept_activity:
            current_app.logger.error(
                f"cannot find accept activity {follow_relation.activity_url}")
            flash(gettext("Whoops, something went wrong"))
            return redirect(url_for("bp_users.profile",
                                    name=current_user.name))
        # Then the Activity ID of the Accept will be the object id
        activity = ap.parse_activity(payload=accept_activity.payload)

        # Get the final activity (the Follow one)
        follow_activity = Activity.query.filter(
            Activity.url == activity.get_object_id()).first()
        if not follow_activity:
            current_app.logger.error(
                f"cannot find follow activity {activity.get_object_id()}")
            flash(gettext("Whoops, something went wrong"))
            return redirect(url_for("bp_users.profile",
                                    name=current_user.name))

        ap_follow_activity = ap.parse_activity(payload=follow_activity.payload)

        # 4. Initiate a Follow request from actor_me to actor_target
        unfollow = ap_follow_activity.build_undo()
        post_to_outbox(unfollow)
        flash(gettext("Unfollow request have been transmitted"), "success")

    return redirect(url_for("bp_users.profile", name=current_user.name))
Exemplo n.º 4
0
def follow():
    user = request.args.get("user")

    actor_me = current_user.actor[0]

    if user.startswith("https://"):
        actor = Actor.query.filter(Actor.url == user).first()
        if actor:
            local_user = actor.user
        else:
            local_user = None
    else:
        local_user = User.query.filter(User.name == user).first()

    if local_user:
        # Process local follow
        actor_me.follow(None, local_user.actor[0])
        flash(gettext("Follow successful"), "success")
    else:
        # Might be a remote follow

        # 1. Webfinger the user
        try:
            remote_actor_url = get_actor_url(user, debug=current_app.debug)
        except InvalidURLError:
            current_app.logger.exception(f"Invalid webfinger URL: {user}")
            remote_actor_url = None
        # except requests.exceptions.HTTPError:
        #    current_app.logger.exception(f"Invali webfinger URL: {user}")
        #    remote_actor_url = None

        if not remote_actor_url:
            flash(gettext("User not found"), "error")
            return redirect(url_for("bp_users.profile",
                                    name=current_user.name))

        # 2. Check if we have a local user
        actor_target = Actor.query.filter(
            Actor.url == remote_actor_url).first()

        if not actor_target:
            # 2.5 Fetch and save remote actor
            backend = ap.get_backend()
            iri = backend.fetch_iri(remote_actor_url)
            if not iri:
                flash(gettext("User not found"), "error")
                return redirect(url_for("bp_main.home"))
            act = ap.parse_activity(iri)
            actor_target, user_target = create_remote_actor(act)
            db.session.add(user_target)
            db.session.add(actor_target)

        # 2.7 Check if we already have a relation
        rel = Follower.query.filter(
            Follower.actor_id == actor_me.id,
            Follower.target_id == actor_target.id).first()

        if not rel:
            # 3. Initiate a Follow request from actor_me to actor_target
            follow = ap.Follow(actor=actor_me.url, object=actor_target.url)
            post_to_outbox(follow)
            flash(gettext("Follow request have been transmitted"), "success")
        else:
            flash(gettext("You already follow this user", "info"))

    return redirect(url_for("bp_users.profile", name=current_user.name))