corpus

self-hosted listenbrainz and last.fm proxy
Log | Files | Refs | README | LICENSE

Api.elm (5769B)


      1 module Api exposing (fetchListens, fetchSectionData, fetchSimilarTracks, fetchStats, httpErrorToString, listensDecoder, sectionEntriesDecoder, similarTracksDecoder, statsDecoder, statsUrl)
      2 
      3 import Http
      4 import Json.Decode as D exposing (Decoder)
      5 import Types exposing (ActiveFilter, Listen, Msg(..), Period(..), SimilarTrack, Stats, StatsEntry)
      6 import Url
      7 
      8 
      9 fetchListens : String -> Int -> Int -> Maybe ActiveFilter -> Maybe String -> Cmd Msg
     10 fetchListens userSlug limit offset mFilter mSearch =
     11     let
     12         filterParams =
     13             case mFilter of
     14                 Nothing ->
     15                     ""
     16 
     17                 Just { field, value } ->
     18                     "&filterField=" ++ field ++ "&filterValue=" ++ Url.percentEncode value
     19 
     20         searchParam =
     21             case mSearch of
     22                 Nothing ->
     23                     ""
     24 
     25                 Just q ->
     26                     "&search=" ++ Url.percentEncode q
     27 
     28         url =
     29             "/proxy?user=" ++ userSlug ++ "&limit=" ++ String.fromInt limit ++ "&offset=" ++ String.fromInt offset ++ filterParams ++ searchParam
     30     in
     31     Http.get
     32         { url = url
     33         , expect = Http.expectJson GotListens listensDecoder
     34         }
     35 
     36 
     37 fetchStats : String -> Period -> Cmd Msg
     38 fetchStats userSlug period =
     39     Http.get
     40         { url = statsUrl userSlug period Nothing
     41         , expect = Http.expectJson GotStats statsDecoder
     42         }
     43 
     44 
     45 fetchSectionData : String -> Period -> String -> Cmd Msg
     46 fetchSectionData userSlug period section =
     47     Http.get
     48         { url = statsUrl userSlug period (Just section)
     49         , expect = Http.expectJson (GotSectionData section) (sectionEntriesDecoder section)
     50         }
     51 
     52 
     53 statsUrl : String -> Period -> Maybe String -> String
     54 statsUrl userSlug period mSection =
     55     let
     56         periodPart =
     57             case period of
     58                 AllTime ->
     59                     ""
     60 
     61                 LastDays n ->
     62                     "&period=" ++ String.fromInt n
     63 
     64                 CustomRange from to ->
     65                     "&from=" ++ from ++ "&to=" ++ to
     66 
     67         sectionPart =
     68             case mSection of
     69                 Nothing ->
     70                     ""
     71 
     72                 Just sec ->
     73                     "&section=" ++ sec
     74     in
     75     "/stats?user=" ++ userSlug ++ periodPart ++ sectionPart
     76 
     77 
     78 fetchSimilarTracks : Int -> String -> String -> Cmd Msg
     79 fetchSimilarTracks idx artist track =
     80     Http.get
     81         { url = "/similar?artist=" ++ Url.percentEncode artist ++ "&track=" ++ Url.percentEncode track
     82         , expect = Http.expectJson (FetchSimilarTracks idx artist track) similarTracksDecoder
     83         }
     84 
     85 
     86 httpErrorToString : Http.Error -> String
     87 httpErrorToString err =
     88     case err of
     89         Http.BadUrl url ->
     90             "Bad URL: " ++ url
     91 
     92         Http.Timeout ->
     93             "Request timed out"
     94 
     95         Http.NetworkError ->
     96             "Network error"
     97 
     98         Http.BadStatus status ->
     99             "Server error: " ++ String.fromInt status
    100 
    101         Http.BadBody body ->
    102             "JSON decode error: " ++ body
    103 
    104 
    105 
    106 -- DECODERS
    107 
    108 
    109 listensDecoder : Decoder (List Listen)
    110 listensDecoder =
    111     D.at [ "payload", "listens" ] (D.list listenDecoder)
    112 
    113 
    114 listenDecoder : Decoder Listen
    115 listenDecoder =
    116     D.map2
    117         (\meta listenedAt ->
    118             { trackName = meta.trackName
    119             , artistName = meta.artistName
    120             , releaseName = meta.releaseName
    121             , listenedAt = listenedAt
    122             , genre = meta.genre
    123             , label = meta.label
    124             , releaseMbid = meta.releaseMbid
    125             , caaReleaseMbid = meta.caaReleaseMbid
    126             }
    127         )
    128         (D.field "track_metadata" trackMetaDecoder)
    129         (D.maybe (D.field "listened_at" D.int))
    130 
    131 
    132 type alias TrackMeta =
    133     { trackName : Maybe String
    134     , artistName : Maybe String
    135     , releaseName : Maybe String
    136     , genre : Maybe String
    137     , releaseMbid : Maybe String
    138     , caaReleaseMbid : Maybe String
    139     , label : Maybe String
    140     }
    141 
    142 
    143 trackMetaDecoder : Decoder TrackMeta
    144 trackMetaDecoder =
    145     D.map7 TrackMeta
    146         (D.maybe (D.field "track_name" D.string))
    147         (D.maybe (D.field "artist_name" D.string))
    148         (D.maybe (D.field "release_name" D.string))
    149         (D.maybe (D.field "genre" D.string))
    150         (D.maybe (D.at [ "mbid_mapping", "release_mbid" ] D.string))
    151         (D.maybe (D.at [ "mbid_mapping", "caa_release_mbid" ] D.string))
    152         (D.maybe (D.field "label" D.string))
    153 
    154 
    155 statsDecoder : Decoder Stats
    156 statsDecoder =
    157     D.map5 Stats
    158         (D.field "genres" (D.list entryDecoder))
    159         (D.field "labels" (D.list entryDecoder))
    160         (D.field "years" (D.list entryDecoder))
    161         (D.field "artists" (D.list entryDecoder))
    162         (D.field "tracks" (D.list entryDecoder))
    163 
    164 
    165 entryDecoder : Decoder StatsEntry
    166 entryDecoder =
    167     D.map2 StatsEntry
    168         (D.field "name" D.string)
    169         (D.field "count" D.int)
    170 
    171 
    172 sectionEntriesDecoder : String -> Decoder (List StatsEntry)
    173 sectionEntriesDecoder section =
    174     D.map
    175         (\stats ->
    176             case section of
    177                 "artist" ->
    178                     stats.artists
    179 
    180                 "track" ->
    181                     stats.tracks
    182 
    183                 "genre" ->
    184                     stats.genres
    185 
    186                 "label" ->
    187                     stats.labels
    188 
    189                 "year" ->
    190                     stats.years
    191 
    192                 _ ->
    193                     []
    194         )
    195         statsDecoder
    196 
    197 
    198 similarTracksDecoder : Decoder (List SimilarTrack)
    199 similarTracksDecoder =
    200     D.at [ "data", "similar_tracks" ] (D.list similarTrackDecoder)
    201 
    202 
    203 similarTrackDecoder : Decoder SimilarTrack
    204 similarTrackDecoder =
    205     D.map4 SimilarTrack
    206         (D.field "artist" D.string)
    207         (D.field "track" D.string)
    208         (D.maybe (D.field "score" D.float))
    209         (D.maybe (D.field "video_uri" D.string))