Exemplo n.º 1
0
def unprocessed():
    """
    User unprocessed tracks timeline.
    ---
    tags:
        - Timelines
    parameters:
        - name: count
          in: query
          type: integer
          required: true
          description: count
        - name: page
          in: query
          type: integer
          description: page number
    responses:
        200:
            description: Returns array of Status
    """
    user = current_token.user
    if not user:
        return jsonify({"error": "Unauthorized"}), 403

    count = int(request.args.get("count", 20))
    page = int(request.args.get("page", 1))

    q = Sound.query.filter(
        Sound.user_id == user.id,
        Sound.transcode_state.in_(
            (Sound.TRANSCODE_WAITING, Sound.TRANSCODE_PROCESSING,
             Sound.TRANSCODE_ERROR)),
    )

    q = q.order_by(Sound.uploaded.desc())

    q = q.paginate(page=page, per_page=count)

    tracks = []
    for t in q.items:
        relationship = to_json_relationship(current_token.user, t.user)
        account = to_json_account(t.user, relationship)
        tracks.append(to_json_track(t, account))
    resp = {
        "page": page,
        "page_size": count,
        "totalItems": q.total,
        "items": tracks,
        "totalPages": q.pages
    }
    return jsonify(resp)
Exemplo n.º 2
0
def edit(username, soundslug):
    """
    Edit track.
    ---
    tags:
        - Tracks
    security:
        - OAuth2:
            - write
    parameters:
        - name: username
          in: path
          type: string
          required: true
          description: User username
        - name: soundslug
          in: path
          type: string
          required: true
          description: Track slug
    responses:
        200:
            description: Returns a Status with extra reel2bits params.
    """
    current_user = current_token.user
    if not current_user:
        return jsonify({"error": "Unauthorized"}), 403

    # Get the track
    sound = Sound.query.filter(Sound.user_id == current_user.id, Sound.slug == soundslug).first()
    if not sound:
        return jsonify({"error": "Not found"}), 404

    album = request.json.get("album")
    description = request.json.get("description")
    licence = request.json.get("licence")
    private = request.json.get("private")
    title = request.json.get("title")
    genre = request.json.get("genre")
    tags = request.json.get("tags")

    if sound.private and not private:
        return jsonify({"error": "Cannot change to private: track already federated"})

    if not title:
        title, _ = splitext(sound.filename_orig)
    else:
        sound.title = title

    sound.description = description
    sound.licence = licence
    sound.genre = genre

    # First remove tags which have been removed
    for tag in sound.tags:
        if tag.name not in tags:
            sound.tags.remove(tag)

    # Then add the new ones if new
    for tag in tags:
        if tag not in [a.name for a in sound.tags]:
            dbt = SoundTag.query.filter(SoundTag.name == tag).first()
            if not dbt:
                dbt = SoundTag(name=tag)
                db.session.add(dbt)
            sound.tags.append(dbt)

    # Purge orphaned tags
    for otag in SoundTag.query.filter(and_(~SoundTag.sounds.any(), ~SoundTag.albums.any())).all():
        db.session.delete(otag)

    # Fetch album, and associate if owner
    if album and (album != "__None"):
        db_album = Album.query.filter(Album.id == album).first()
        if db_album and (db_album.user_id == current_user.id):
            sound.album_id = db_album.id
            if not db_album.sounds:
                sound.album_order = 0
            else:
                sound.album_order = db_album.sounds.count() + 1
    elif album == "__None":
        sound.album_id = None
        sound.album_order = 0

    db.session.commit()

    # trigger a sound update
    send_update_sound(sound)

    relationship = False
    if current_token and current_token.user:
        relationship = to_json_relationship(current_token.user, sound.user)
    account = to_json_account(sound.user, relationship)
    return jsonify(to_json_track(sound, account))
Exemplo n.º 3
0
def show(username_or_id, soundslug):
    """
    Get track details.
    ---
    tags:
        - Tracks
    parameters:
        - name: user_id
          in: path
          type: integer
          required: true
          description: User ID
        - name: soundslug
          in: path
          type: string
          required: true
          description: Track slug
    responses:
        200:
            description: Returns track details.
    """
    # 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

    # Get the associated User from url fetch
    track_user = User.query.filter(User.name == username_or_id, User.local.is_(True)).first()
    if not track_user:
        try:
            track_user = User.query.filter(User.flake_id == username_or_id).first()
        except sqlalchemy.exc.DataError:
            return jsonify({"error": "User not found"}), 404
    if not track_user:
        return jsonify({"error": "User not found"}), 404

    if current_user and (track_user.id == current_user.id):
        print("user")
        sound = Sound.query.filter(Sound.slug == soundslug, Sound.user_id == track_user.id).first()
    else:
        print("no user")
        sound = Sound.query.filter(
            Sound.slug == soundslug, Sound.user_id == track_user.id, Sound.transcode_state == Sound.TRANSCODE_DONE
        ).first()

    if not sound:
        print("mmmh")
        return jsonify({"error": "not found"}), 404

    if sound.private:
        if current_user:
            if sound.user_id != current_user.id:
                return jsonify({"error": "forbidden"}), 403
        else:
            return jsonify({"error": "forbidden"}), 403

    relationship = False
    if current_token and current_token.user:
        relationship = to_json_relationship(current_token.user, sound.user)
    account = to_json_account(sound.user, relationship)
    return jsonify(to_json_track(sound, account))
Exemplo n.º 4
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.º 5
0
def user_statuses(user_id):
    """
    User statuses.
    ---
    tags:
        - Timelines
    parameters:
        - name: count
          in: query
          type: integer
          required: true
          description: count per page
        - name: with_muted
          in: query
          type: boolean
          required: true
          description: with muted users
        - name: page
          in: query
          type: integer
          description: page number
    responses:
        200:
            description: Returns array of Status
    """
    # Caveats: only handle public Sounds since we either federate (public) or no
    count = int(request.args.get("count", 20))
    page = int(request.args.get("page", 1))

    # Get associated user
    user = User.query.filter(User.id == user_id).first()
    if not user:
        abort(404)

    q = db.session.query(Activity, Sound).filter(
        Activity.type == "Create",
        Activity.payload[("object", "type")].astext == "Audio")
    q = q.filter(Activity.meta_deleted.is_(False))

    q = q.filter(Activity.payload["to"].astext.contains(
        "https://www.w3.org/ns/activitystreams#Public"))

    q = q.filter(Activity.actor == user.actor[0].id)

    q = q.join(Sound, Sound.activity_id == Activity.id)
    q = q.order_by(Activity.creation_date.desc())

    q = q.paginate(page=page, per_page=count)

    tracks = []
    for t in q.items:
        if t.Sound:
            relationship = False
            if current_token and current_token.user:
                relationship = to_json_relationship(current_token.user,
                                                    t.Sound.user)
            account = to_json_account(t.Sound.user, relationship)
            tracks.append(to_json_track(t.Sound, account))
        else:
            print(t.Activity)
    resp = {
        "page": page,
        "page_size": count,
        "totalItems": q.total,
        "items": tracks,
        "totalPages": q.pages
    }
    return jsonify(resp)
Exemplo n.º 6
0
def public():
    """
    Public or TWKN statuses.
    ---
    tags:
        - Timelines
    parameters:
        - name: count
          in: query
          type: integer
          required: true
          description: count
        - name: with_muted
          in: query
          type: boolean
          required: true
          description: with muted users
        - name: local
          in: query
          type: boolean
          description: local only or TWKN
    responses:
        200:
            description: Returns array of Status
    """
    # Caveats: only handle public Sounds since we either federate (public) or no

    paginated = request.args.get("paginated", False)
    count = int(request.args.get("count", 20))
    local_only = request.args.get("local", False)

    q = db.session.query(Activity, Sound).filter(
        Activity.type == "Create", Activity.payload[("object", "type")].astext == "Audio"
    )
    q = q.filter(Activity.meta_deleted.is_(False))

    if local_only:
        q = q.filter(Activity.local.is_(True))

    q = q.filter(Activity.payload["to"].astext.contains("https://www.w3.org/ns/activitystreams#Public"))

    q = q.join(Sound, Sound.activity_id == Activity.id)
    q = q.order_by(Activity.creation_date.desc())

    if paginated:
        # Render timeline as paginated
        page = int(request.args.get("page", 1))

        q = q.paginate(page=page, per_page=count)

        tracks = []
        for t in q.items:
            if t.Sound:
                # TODO(dashie) FIXME can probably be moved out to the q.filter()
                if not t.Sound.transcode_state == Sound.TRANSCODE_DONE:
                    continue
                relationship = False
                if current_token and current_token.user:
                    relationship = to_json_relationship(current_token.user, t.Sound.user)
                account = to_json_account(t.Sound.user, relationship)
                tracks.append(to_json_track(t.Sound, account))
            else:
                print(t.Activity)
        resp = {"page": page, "page_size": count, "totalItems": q.total, "items": tracks, "totalPages": q.pages}
        return jsonify(resp)
    else:
        # mastoapi compatible
        since_id = request.args.get("since_id")

        # since then we want the timeline
        if since_id:
            q = q.filter(Sound.flake_id > since_id)

        # then limit count
        q = q.limit(count)

        tracks = []
        for t in q.all():
            if t.Sound:
                relationship = False
                if current_token and current_token.user:
                    relationship = to_json_relationship(current_token.user, t.Sound.user)
                account = to_json_account(t.Sound.user, relationship)
                tracks.append(to_json_track(t.Sound, account))
            else:
                print(t.Activity)
        return jsonify(tracks)