feat: server /fetch resolves non-direct links via Odesli (Lidarr-first)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,7 @@ def fetch(q: str = Query(..., min_length=1),
|
|||||||
raise HTTPException(status_code=422, detail=f"Invalid quality '{quality}'.")
|
raise HTTPException(status_code=422, detail=f"Invalid quality '{quality}'.")
|
||||||
|
|
||||||
if mf.is_url(q):
|
if mf.is_url(q):
|
||||||
|
if mf._is_direct_url(q):
|
||||||
kind, title, hits = mf.probe_url(q)
|
kind, title, hits = mf.probe_url(q)
|
||||||
syn = mf.Hit(source="youtube", kind=kind, title=title, artist="")
|
syn = mf.Hit(source="youtube", kind=kind, title=title, artist="")
|
||||||
job = jobs.create_job(hit=syn, message=actions.url_started_message(kind, title))
|
job = jobs.create_job(hit=syn, message=actions.url_started_message(kind, title))
|
||||||
@@ -66,6 +67,28 @@ def fetch(q: str = Query(..., min_length=1),
|
|||||||
)
|
)
|
||||||
return response
|
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)
|
||||||
|
jobs.run_job(
|
||||||
|
job.id,
|
||||||
|
lambda: actions.perform_fetch(chosen, hits, quality, ROOT),
|
||||||
|
done_message=actions.done_message(chosen),
|
||||||
|
fail_message=actions.failed_message(chosen),
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
if source not in ("auto", "lidarr", "youtube"):
|
if source not in ("auto", "lidarr", "youtube"):
|
||||||
raise HTTPException(status_code=422, detail=f"Invalid source '{source}'.")
|
raise HTTPException(status_code=422, detail=f"Invalid source '{source}'.")
|
||||||
|
|
||||||
|
|||||||
@@ -73,3 +73,43 @@ def test_search_query_still_works(client, auth, monkeypatch):
|
|||||||
r = client.post("/fetch", params={"q": "Daft Punk - Discovery"}, headers=auth)
|
r = client.post("/fetch", params={"q": "Daft Punk - Discovery"}, headers=auth)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json()["status"] == "queued"
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user