1module LastFm exposing (..)
2
3import Common
4import Http
5import Json.Decode as Json
6import List.Ext as List
7import MD5
8import String.Ext as String
9import Tracks exposing (Track)
10import Tuple.Ext as Tuple
11import Url exposing (Url)
12import Url.Ext as Url
13
14
15
16-- 🏔
17
18
19apiKey =
20 "4f0fe85b67baef8bb7d008a8754a95e5"
21
22
23apiUrl =
24 "https://ws.audioscrobbler.com/2.0"
25
26
27notSoSecret =
28 "0cec3ca0f58e04a5082f1131aba1e0d3"
29
30
31
32-- 🌳
33
34
35type alias Model =
36 { authenticating : Bool
37 , sessionKey : Maybe String
38 }
39
40
41initialModel : Model
42initialModel =
43 { authenticating = False
44 , sessionKey = Nothing
45 }
46
47
48authenticationCommand : (Result Http.Error String -> msg) -> Url -> Cmd msg
49authenticationCommand msg url =
50 case Url.extractQueryParam "token" url of
51 Just token ->
52 Http.get
53 { url =
54 authenticatedUrl
55 [ ( "method", "auth.getSession" )
56 , ( "token", token )
57 ]
58 , expect =
59 Json.string
60 |> Json.at [ "session", "key" ]
61 |> Http.expectJson msg
62 }
63
64 Nothing ->
65 Cmd.none
66
67
68
69-- 📣
70
71
72disconnect : Model -> Model
73disconnect model =
74 { model | sessionKey = Nothing }
75
76
77failedToAuthenticate : Model -> Model
78failedToAuthenticate model =
79 { model | authenticating = False }
80
81
82gotSessionKey : String -> Model -> Model
83gotSessionKey sessionKey model =
84 { model | sessionKey = Just sessionKey }
85
86
87
88-- 🎵
89
90
91nowPlaying : Model -> { duration : Int, msg : msg, track : Track } -> Cmd msg
92nowPlaying model { duration, msg, track } =
93 case model.sessionKey of
94 Just sessionKey ->
95 Http.post
96 { url =
97 apiUrl
98 , body =
99 [ ( "duration", String.fromInt duration )
100 , ( "track", track.tags.title )
101 , ( "trackNumber", String.fromInt track.tags.nr )
102
103 --
104 , ( "method", "track.updateNowPlaying" )
105 , ( "sk", sessionKey )
106 ]
107 |> addAlbum track
108 |> addArtist track
109 |> authenticatedBody
110 , expect =
111 Http.expectWhatever (always msg)
112 }
113
114 Nothing ->
115 Cmd.none
116
117
118scrobble : Model -> { duration : Int, msg : msg, timestamp : Int, track : Track } -> Cmd msg
119scrobble model { duration, msg, timestamp, track } =
120 case model.sessionKey of
121 Just sessionKey ->
122 Http.post
123 { url =
124 apiUrl
125 , body =
126 [ ( "duration", String.fromInt duration )
127 , ( "track", track.tags.title )
128 , ( "trackNumber", String.fromInt track.tags.nr )
129
130 --
131 , ( "method", "track.scrobble" )
132 , ( "sk", sessionKey )
133 , ( "timestamp", String.fromInt timestamp )
134 ]
135 |> addAlbum track
136 |> addArtist track
137 |> authenticatedBody
138 , expect =
139 Http.expectWhatever (always msg)
140 }
141
142 Nothing ->
143 Cmd.none
144
145
146addAlbum track list =
147 case track.tags.album of
148 Just album ->
149 ( "album", album ) :: list
150
151 Nothing ->
152 list
153
154
155addArtist track list =
156 case track.tags.artist of
157 Just artist ->
158 ( "artist", artist ) :: list
159
160 Nothing ->
161 list
162
163
164
165-- 🔱
166
167
168authenticatedBody : List ( String, String ) -> Http.Body
169authenticatedBody params =
170 params
171 |> authenticatedParams
172 |> Common.queryString
173 |> String.dropLeft 1
174 |> Http.stringBody "application/x-www-form-urlencoded"
175
176
177authenticatedUrl : List ( String, String ) -> String
178authenticatedUrl params =
179 params
180 |> authenticatedParams
181 |> Common.queryString
182 |> String.append apiUrl
183
184
185authenticatedParams : List ( String, String ) -> List ( String, String )
186authenticatedParams params =
187 let
188 extendedParams =
189 ( "api_key", apiKey ) :: params
190 in
191 extendedParams
192 |> List.sortBy Tuple.first
193 |> List.map (Tuple.uncurry String.append)
194 |> String.concat
195 |> String.addSuffix notSoSecret
196 |> MD5.hex
197 |> Tuple.pair "api_sig"
198 |> List.addTo extendedParams
199 |> (::) ( "format", "json" )
200 |> List.sortBy Tuple.first