149 lines
4.8 KiB
Elixir
149 lines
4.8 KiB
Elixir
defmodule MusicLibrary.ScrobbleActivity do
|
|
@moduledoc """
|
|
Scrobbling releases, media, and tracks to Last.fm.
|
|
"""
|
|
|
|
alias LastFm.Scrobble
|
|
alias MusicBrainz.Release
|
|
alias MusicLibrary.Secrets
|
|
|
|
@spec can_scrobble?() :: boolean()
|
|
def can_scrobble? do
|
|
Secrets.get("last_fm_session_key") !== nil
|
|
end
|
|
|
|
@spec scrobble_release(map(), :started_at | :finished_at, DateTime.t()) ::
|
|
{:ok, term()} | {:error, term()}
|
|
def scrobble_release(release_with_tracks, :finished_at, finished_at) do
|
|
release_duration = Release.release_duration(release_with_tracks)
|
|
started_at = DateTime.add(finished_at, -release_duration, :millisecond)
|
|
scrobble_release(release_with_tracks, :started_at, started_at)
|
|
end
|
|
|
|
def scrobble_release(release_with_tracks, :started_at, started_at) do
|
|
release_duration = Release.release_duration(release_with_tracks)
|
|
|
|
if release_duration == 0 do
|
|
{:error, :no_duration}
|
|
else
|
|
with {:ok, session_key} <- fetch_session_key() do
|
|
{scrobbles, _finished_at} =
|
|
release_with_tracks
|
|
|> MusicBrainz.Release.tracks()
|
|
|> to_scrobbles(release_with_tracks, started_at)
|
|
|
|
LastFm.scrobble(scrobbles, session_key)
|
|
end
|
|
end
|
|
end
|
|
|
|
@spec scrobble_medium(integer(), map(), :started_at | :finished_at, DateTime.t()) ::
|
|
{:ok, term()} | {:error, term()}
|
|
def scrobble_medium(number, release_with_tracks, :finished_at, finished_at) do
|
|
case find_medium(release_with_tracks, number) do
|
|
{:ok, medium} ->
|
|
medium_duration = Release.medium_duration(medium)
|
|
started_at = DateTime.add(finished_at, -medium_duration, :millisecond)
|
|
scrobble_medium(number, release_with_tracks, :started_at, started_at)
|
|
|
|
{:error, :medium_not_found} ->
|
|
{:error, :medium_not_found}
|
|
end
|
|
end
|
|
|
|
def scrobble_medium(number, release_with_tracks, :started_at, started_at) do
|
|
case find_medium(release_with_tracks, number) do
|
|
{:ok, medium} ->
|
|
medium_duration = Release.medium_duration(medium)
|
|
|
|
if medium_duration == 0 do
|
|
{:error, :no_duration}
|
|
else
|
|
with {:ok, session_key} <- fetch_session_key() do
|
|
{scrobbles, _finished_at} =
|
|
medium.tracks
|
|
|> to_scrobbles(release_with_tracks, started_at)
|
|
|
|
LastFm.scrobble(scrobbles, session_key)
|
|
end
|
|
end
|
|
|
|
{:error, :medium_not_found} ->
|
|
{:error, :medium_not_found}
|
|
end
|
|
end
|
|
|
|
@spec scrobble_tracks(MapSet.t(), map(), :started_at | :finished_at, DateTime.t()) ::
|
|
{:ok, term()} | {:error, term()}
|
|
def scrobble_tracks(selected_track_ids, release_with_tracks, :finished_at, finished_at) do
|
|
all_tracks = Release.tracks(release_with_tracks)
|
|
|
|
selected_tracks =
|
|
Enum.filter(all_tracks, fn track -> MapSet.member?(selected_track_ids, track.id) end)
|
|
|
|
tracks_duration = Enum.sum_by(selected_tracks, fn track -> track.length || 0 end)
|
|
started_at = DateTime.add(finished_at, -tracks_duration, :millisecond)
|
|
scrobble_tracks(selected_track_ids, release_with_tracks, :started_at, started_at)
|
|
end
|
|
|
|
def scrobble_tracks(selected_track_ids, release_with_tracks, :started_at, started_at) do
|
|
all_tracks = Release.tracks(release_with_tracks)
|
|
|
|
selected_tracks =
|
|
Enum.filter(all_tracks, fn track -> MapSet.member?(selected_track_ids, track.id) end)
|
|
|
|
tracks_duration = Enum.sum_by(selected_tracks, fn track -> track.length || 0 end)
|
|
|
|
if tracks_duration == 0 do
|
|
{:error, :no_duration}
|
|
else
|
|
with {:ok, session_key} <- fetch_session_key() do
|
|
{scrobbles, _finished_at} =
|
|
selected_tracks
|
|
|> to_scrobbles(release_with_tracks, started_at)
|
|
|
|
LastFm.scrobble(scrobbles, session_key)
|
|
end
|
|
end
|
|
end
|
|
|
|
defp fetch_session_key do
|
|
case Secrets.get("last_fm_session_key") do
|
|
%{value: value} -> {:ok, value}
|
|
nil -> {:error, :no_session_key}
|
|
end
|
|
end
|
|
|
|
defp to_scrobbles(tracks, release_with_tracks, started_at) do
|
|
tracks
|
|
|> Enum.map_reduce(started_at, fn track, time ->
|
|
album_artist =
|
|
if release_with_tracks.artists !== track.artists do
|
|
main_artist_name(release_with_tracks.artists)
|
|
end
|
|
|
|
time = time |> DateTime.add(track.length, :millisecond)
|
|
|
|
scrobble = %Scrobble{
|
|
artist: main_artist_name(track.artists),
|
|
album: release_with_tracks.title,
|
|
album_artist: album_artist,
|
|
track: track.title,
|
|
timestamp: DateTime.to_unix(time)
|
|
}
|
|
|
|
{scrobble, time}
|
|
end)
|
|
end
|
|
|
|
defp find_medium(release_with_tracks, number) do
|
|
case Enum.find(release_with_tracks.media, fn medium -> medium.number == number end) do
|
|
nil -> {:error, :medium_not_found}
|
|
medium -> {:ok, medium}
|
|
end
|
|
end
|
|
|
|
defp main_artist_name([]), do: nil
|
|
defp main_artist_name([artist | _rest]), do: artist.name
|
|
end
|