-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLastFm.hs
132 lines (105 loc) · 4.44 KB
/
LastFm.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
module LastFm where
import Control.Applicative
import Control.Monad
import qualified Data.ByteString.UTF8 as U
import qualified Data.Map as M
import Data.List
import Data.Digest.OpenSSL.MD5
import System.Time
import System.IO
import qualified Text.JSON as J
import Text.JSON
import Network.URI
import Network.HTTP
import qualified Network.Stream as N
import Network.Stream
-- Configuration
api_uri, post_uri :: URI
api_uri = URI "http:" (Just $ URIAuth "" "ws.audioscrobbler.com" "") "/2.0/?format=json&" "" ""
post_uri = URI "http:" (Just $ URIAuth "" "post.audioscrobbler.com" "") "/?" "" ""
-- Types
type Error = String
type User = String
type Key = String
type Secret = String
type Token = String
type Params = M.Map String String
data Session = Session User Key deriving Show
data Handshake = Handshake { session_key :: Key
, np_url :: String
, submission_url :: String
} deriving (Show)
data ClientConf = ClientConf { api_key :: Key
, api_secret :: Secret
, client_id :: String
, client_version :: String
}
-- JSON
instance JSON Session where
readJSON (JSObject obj) =
lookupObj "session" obj >>=
(\obj' -> Session <$> lookupObj "name" obj' <*> lookupObj "key" obj')
showJSON = undefined
lookupObj :: (JSON a) => String -> JSObject JSValue -> J.Result a
lookupObj key = maybe (Error $ "Key '" ++ key ++ "' not found in object") readJSON
. lookup key
. fromJSObject
decode' :: (JSON a) => String -> N.Result a
decode' = convertResult . decode
where convertResult (Ok a) = Right a
convertResult (Error e) = Left $ ErrorMisc e
-- Session
getSession :: ClientConf -> Token -> IO (N.Result Session)
getSession conf token = do
result <- simpleHTTP $ Request (api_uri { uriQuery = sessionRequest conf token }) GET [] ""
case result of
(Left err) -> return $ Left err
(Right response) -> do
case rspCode response of
-- (2,0,0) -> return $ Right (rspBody response)
(2,0,0) -> return $ decode' (rspBody response)
_ -> return $ Left (ErrorMisc $ "Getting session failed" ++ rspBody response)
sessionRequest :: ClientConf -> Token -> String
sessionRequest conf token = flattenParams $ M.insert "api_sig" (signRequest request (api_secret conf)) request
where (*) = (,)
request = M.fromList
[ "method" * "auth.getSession"
, "token" * token
, "api_key" * api_key conf
]
signRequest :: Params -> Secret -> String
signRequest params secret = md5sum $ U.fromString $ concatMap (\(k, v) -> k ++ v) (M.toAscList params) ++ secret
-- Handshake
getHandshake :: ClientConf -> Session -> IO (N.Result Handshake)
getHandshake conf session = do
query <- handshakeQuery conf session
result <- simpleHTTP $ Request (post_uri { uriQuery = query }) GET [] ""
case result of
(Left err) -> return $ Left err
(Right response) ->
case rspCode response of
-- (2,0,0) -> return $ Right (rspBody response)
(2,0,0) -> return $ parseHandshake $ rspBody response
_ -> return $ Left (ErrorMisc $ "Getting handshake failed: " ++ rspBody response)
handshakeQuery :: ClientConf -> Session -> IO String
handshakeQuery conf (Session username session_key) = do
timestamp <- liftM (\(TOD unix _) -> show unix) getClockTime
return $ flattenParams $ M.fromList -- Uhg
[ "hs" * "true"
, "p" * "1.2.1"
, "c" * client_id conf
, "v" * client_version conf
, "u" * username
, "t" * timestamp
, "a" * md5sum (U.fromString $ (api_secret conf) ++ timestamp)
, "api_key" * api_key conf
, "sk" * session_key
]
where (*) = (,)
parseHandshake :: String -> N.Result Handshake
parseHandshake response = case lines response of
["OK", key, npurl, surl] -> Right $ Handshake key npurl surl
err -> Left $ ErrorMisc ("Handshake failed: " ++ (unlines err))
-- Utils
flattenParams :: Params -> String
flattenParams = concat . intersperse "&" . map (\(k, v) -> k ++ '=' : v) . M.toList