diff --git a/lib/last_fm.ex b/lib/last_fm.ex index 43866b33..a21adfe5 100644 --- a/lib/last_fm.ex +++ b/lib/last_fm.ex @@ -47,6 +47,12 @@ defmodule LastFm do API.get_session(token, last_fm_config) end + def scrobble(tracks, session_key) do + last_fm_config = last_fm_config() + + API.scrobble(tracks, session_key, last_fm_config) + end + def auth_url do last_fm_config = last_fm_config() "https://www.last.fm/api/auth/?api_key=" <> last_fm_config.api_key diff --git a/lib/last_fm/api.ex b/lib/last_fm/api.ex index c6025709..95ee68ba 100644 --- a/lib/last_fm/api.ex +++ b/lib/last_fm/api.ex @@ -75,6 +75,37 @@ defmodule LastFm.API do |> get_request() end + def scrobble(tracks, session_key, config) do + params = + %{"api_key" => config.api_key, "method" => "track.scrobble", "sk" => session_key} + + track_params = + tracks + |> Enum.with_index() + |> Enum.flat_map(fn {track, index} -> + track + |> Enum.map(fn {key, value} -> + {"#{key}[#{index}]", value} + end) + end) + |> Enum.into(%{}) + + params = + params + |> Map.merge(track_params) + + sorted_params = Enum.sort(params) + + signature = signature(sorted_params, config.shared_secret) + + body = Map.merge(params, %{"api_sig" => signature, "format" => "json"}) + + config + |> new_request() + |> Req.merge(url: "/", form: body) + |> post_request() + end + defp signature(params, shared_secret) do encoded_params = Enum.map_join(params, fn {key, value} -> @@ -175,6 +206,19 @@ defmodule LastFm.API do end end + defp post_request(request) do + case Req.post(request) do + {:ok, %{body: %ErrorResponse{} = error_response}} -> + {:error, error_response.error} + + {:ok, response} -> + {:ok, response.body} + + error -> + error + end + end + defp log_attempt(request) do url = URI.to_string(request.url) api_key = Req.Request.get_private(request, :api_key) diff --git a/test/last_fm_test.exs b/test/last_fm_test.exs index ed8e72d9..901d18bd 100644 --- a/test/last_fm_test.exs +++ b/test/last_fm_test.exs @@ -20,4 +20,45 @@ defmodule LastFmTest do assert {:ok, expected_info} == LastFm.get_artist_info(musicbrainz_id, name) end end + + describe "scrobble/2" do + test "it returns the scrobbled track" do + tracks = [ + %{ + track: "Wonderland", + artist: "IQ", + album: "Dominion", + timestamp: 1_746_561_301, + mbid: "aefaaf73-b52c-4b0d-91df-f8e4321db2bd" + } + ] + + session_key = "session_key" + + response_body = %{ + "scrobbles" => %{ + "@attr" => %{"accepted" => 1, "ignored" => 0}, + "scrobble" => %{ + "album" => %{"#text" => "Dominion", "corrected" => "0"}, + "albumArtist" => %{"#text" => "", "corrected" => "0"}, + "artist" => %{"#text" => "IQ", "corrected" => "0"}, + "ignoredMessage" => %{"#text" => "", "code" => "1"}, + "timestamp" => "1746561301", + "track" => %{"#text" => "Wonderland", "corrected" => "0"} + } + } + } + + Req.Test.stub(LastFm.API, fn conn -> + {:ok, body, conn} = Plug.Conn.read_body(conn) + + assert body == + "album%5B0%5D=Dominion&api_key=48df017be28e54860d2f3756b229c91a&api_sig=3654e07703b66d58e7c6f36a3eff7747&artist%5B0%5D=IQ&format=json&mbid%5B0%5D=aefaaf73-b52c-4b0d-91df-f8e4321db2bd&method=track.scrobble&sk=session_key×tamp%5B0%5D=1746561301&track%5B0%5D=Wonderland" + + Req.Test.json(conn, response_body) + end) + + assert {:ok, response_body} == LastFm.scrobble(tracks, session_key) + end + end end