diff --git a/server/app.py b/server/app.py index a3305cc..2b371b8 100644 --- a/server/app.py +++ b/server/app.py @@ -53,16 +53,39 @@ def fetch(q: str = Query(..., min_length=1), raise HTTPException(status_code=422, detail=f"Invalid quality '{quality}'.") if mf.is_url(q): - kind, title, hits = mf.probe_url(q) - syn = mf.Hit(source="youtube", kind=kind, title=title, artist="") - job = jobs.create_job(hit=syn, message=actions.url_started_message(kind, title)) + if mf._is_direct_url(q): + kind, title, hits = mf.probe_url(q) + syn = mf.Hit(source="youtube", kind=kind, title=title, artist="") + job = jobs.create_job(hit=syn, message=actions.url_started_message(kind, title)) + response = _job_public(job) + done_msg = actions.playlist_done_message if kind == "playlist" else actions.url_done_message + jobs.run_job( + job.id, + lambda: actions.perform_url_fetch(q, kind, title, hits, quality, ROOT), + done_message=done_msg, + fail_message="Download failed.", + ) + return response + + # Non-direct link (Spotify/Apple/...): resolve via Odesli, then run the + # normal Lidarr-first pick/dispatch with the exact YouTube track fallback. + try: + query, hits = mf.resolve_link_hits(q, 10) + except mf.OdesliError: + raise HTTPException(status_code=422, + detail=f"Couldn't resolve {q}. Try the direct YouTube or SoundCloud link.") + if not hits: + raise HTTPException(status_code=404, detail=f"No results found for '{q}'.") + chosen = mf.pick(hits, query, True, False) + if chosen is None: + raise HTTPException(status_code=404, detail=f"No results found for '{q}'.") + job = jobs.create_job(hit=chosen, message=actions.started_message(chosen)) response = _job_public(job) - done_msg = actions.playlist_done_message if kind == "playlist" else actions.url_done_message jobs.run_job( job.id, - lambda: actions.perform_url_fetch(q, kind, title, hits, quality, ROOT), - done_message=done_msg, - fail_message="Download failed.", + lambda: actions.perform_fetch(chosen, hits, quality, ROOT), + done_message=actions.done_message(chosen), + fail_message=actions.failed_message(chosen), ) return response diff --git a/tests/test_api_url.py b/tests/test_api_url.py index 24a0b2f..ec841fa 100644 --- a/tests/test_api_url.py +++ b/tests/test_api_url.py @@ -73,3 +73,43 @@ def test_search_query_still_works(client, auth, monkeypatch): r = client.post("/fetch", params={"q": "Daft Punk - Discovery"}, headers=auth) assert r.status_code == 200 assert r.json()["status"] == "queued" + + +def test_non_direct_link_resolves_and_fetches(client, auth, monkeypatch): + from server import mf + lid = mf.Hit(source="lidarr", kind="album", title="A Moment Apart", + artist="ODESZA", album="A Moment Apart", year="2017", + payload={"album": {"id": 9}}) + yt = mf.Hit(source="youtube", kind="track", title="Bloom", artist="ODESZA", + payload={"url": "https://music.youtube.com/watch?v=YYY"}) + monkeypatch.setattr("server.app.mf.resolve_link_hits", + lambda url, limit: ("ODESZA - Bloom", [lid, yt])) + monkeypatch.setattr("server.app.mf.pick", lambda hits, q, ni, yf: hits[0]) + monkeypatch.setattr("server.app.actions.perform_fetch", + lambda chosen, hits, quality, root: {"path": None, "lidarr_album_id": 9}) + r = client.post("/fetch", params={"q": "https://open.spotify.com/track/abc"}, headers=auth) + assert r.status_code == 200 + body = r.json() + assert body["status"] == "queued" + assert body["hit"]["album"] == "A Moment Apart" + done = _wait_done(client, auth, body["job_id"]) + assert done["status"] == "done" + + +def test_non_direct_link_resolve_failure_422(client, auth, monkeypatch): + from server import mf as mf_mod + + def boom(url, limit): + raise mf_mod.OdesliError(url) + + monkeypatch.setattr("server.app.mf.resolve_link_hits", boom) + r = client.post("/fetch", params={"q": "https://open.spotify.com/track/bad"}, headers=auth) + assert r.status_code == 422 + assert "resolve" in r.json()["message"].lower() + + +def test_non_direct_link_no_hits_404(client, auth, monkeypatch): + monkeypatch.setattr("server.app.mf.resolve_link_hits", + lambda url, limit: ("ODESZA - Bloom", [])) + r = client.post("/fetch", params={"q": "https://open.spotify.com/track/abc"}, headers=auth) + assert r.status_code == 404