packages feed

rdioh 0.1.1 → 0.1.2

raw patch · 7 files changed

+1271/−216 lines, 7 filesdep +aesondep +containersdep +hspecdep ~basenew-component:exe:rdiohnew-component:exe:rdioh-spec

Dependencies added: aeson, containers, hspec, rdioh, transformers

Dependency ranges changed: base

Files

rdioh.cabal view
@@ -1,5 +1,5 @@ name: rdioh-version: 0.1.1+version: 0.1.2 category: Network, API, Web license: MIT license-file: LICENSE@@ -7,25 +7,28 @@ maintainer: Aditya Bhargava <aditya at wefoundland.com> stability: experimental build-type: Simple-cabal-version: >= 1.6+cabal-version: >= 1.8 synopsis: A Haskell wrapper for Rdio's API. description: This library implements methods for Rdio's API.-			 It supports 2-legged and 3-legged OAuth.+			 It supports 2-legged and 3-legged OAuth. See Rdio's API for reference: http://developer.rdio.com/docs/read/rest/Methods  library-  build-depends: base >= 3 && <= 5-               , mtl-               , MissingH-               , bytestring-               , json-               , hoauth-               , urlencoded-  exposed-modules: Rdioh, RdioResult+  build-depends: base >= 3 && <= 5 , mtl , MissingH , bytestring , json , hoauth , urlencoded, aeson, containers, transformers+  exposed-modules: Rdioh, Rdioh.Models, Rdioh.Auth    hs-source-dirs: src-  ghc-options: -W -Wall   extensions: FlexibleContexts++executable rdioh+  build-depends: base >= 3 && <= 5 , mtl , MissingH , bytestring , json , hoauth , urlencoded, aeson, containers, transformers+  hs-source-dirs: src+  main-is: Main.hs+ source-repository head   type:     git   location: git@github.com:egonSchiele/rdioh.git +executable rdioh-spec+  build-depends:       base ==4.5.*, hspec, rdioh, mtl , MissingH , bytestring , json , hoauth , urlencoded, aeson, containers, transformers+  hs-source-dirs:      spec, src+  main-is:             Main.hs
+ spec/Main.hs view
@@ -0,0 +1,78 @@+import Test.Hspec+import Rdioh+import Rdioh.Models+import System.IO.Unsafe+import Data.String.Utils+import Data.Aeson+import qualified Debug.Trace as D++key = strip . unsafePerformIO . readFile $ "key"+secret = strip . unsafePerformIO . readFile $ "secret"++testMethod :: (Show a, FromJSON a) => Rdio (Either String a) -> Bool+testMethod meth = unsafePerformIO $ do+    result <- runRdio key secret meth+    case result of+      Left err -> fail err+      Right x -> return True++radiohead = "r91318"+me = "s361565"+emi = "l202397"++okcomputer = "a171828"+okcomputerShortCode = "QVrGAiJdzXU"+okComputerUrl = "/artist/Radiohead/album/OK_Computer_(Collector%27s_Edition)/"++main = hspec $ do+  describe "make sure methods return valid models" $ do+    it "get" $ do+      testMethod $ (get [radiohead] [] :: Rdio (Either String Object))+    it "getObjectFromShortCode" $ do+      testMethod $ (getObjectFromShortCode okcomputerShortCode [] :: Rdio (Either String Album))+    it "getObjectFromUrl" $ do+      testMethod $ (getObjectFromUrl okComputerUrl [] :: Rdio (Either String Album))+    it "getAlbumsByUPC" $ do+      testMethod $ getAlbumsByUPC 654979031628 []+    it "getAlbumsForArtist" $ do+      testMethod $ getAlbumsForArtist radiohead+    it "getAlbumsForLabel" $ do+      testMethod $ getAlbumsForLabel emi+    it "getArtistsForLabel" $ do+      testMethod $ getArtistsForLabel emi+    it "getTracksByISRC" $ do+      testMethod $ getTracksByISRC "" []+    it "getTracksForArtist" $ do+      testMethod $ getTracksForArtist radiohead+    it "search" $ do+      testMethod $ (search "Radiohead" "Artist" :: Rdio (Either String [Artist]))+    it "getAlbumsForArtistInCollection'" $ do+      testMethod $ getAlbumsForArtistInCollection' radiohead (Just me) [] Nothing -- me+    it "getAlbumsInCollection'" $ do+      testMethod $ getAlbumsInCollection' (Just me) Nothing Nothing Nothing Nothing []+    it "getArtistsInCollection'" $ do+      testMethod $ getArtistsInCollection' (Just me) Nothing Nothing Nothing Nothing []+    it "getTracksForAlbumInCollection'" $ do+      testMethod $ getTracksForAlbumInCollection' okcomputer (Just me) []+    it "getTracksForArtistInCollection'" $ do+      testMethod $ getTracksForArtistInCollection' radiohead (Just me) []+    it "getTracksInCollection'" $ do+      testMethod $ getTracksInCollection' (Just me) Nothing Nothing Nothing Nothing []+    it "getPlaylists'" $ do+      testMethod $ getPlaylists' (Just me) [] Nothing+    it "getUserPlaylists" $ do+      testMethod $ getUserPlaylists me+    it "findUserByEmail" $ do+      testMethod $ findUserByEmail "bluemangroupie@gmail.com" []+    it "findUserByName" $ do+      testMethod $ findUserByName "egonschiele" []+    it "getNewReleases" $ do+      testMethod $ getNewReleases+    it "getTopChartArtists" $ do+      testMethod $ getTopChartArtists+    it "getTopChartAlbums" $ do+      testMethod $ getTopChartAlbums+    it "getTopChartTracks" $ do+      testMethod $ getTopChartTracks+    it "getTopChartPlaylists" $ do+      testMethod $ getTopChartPlaylists
+ src/Main.hs view
@@ -0,0 +1,19 @@+import Rdioh+import System.IO.Unsafe+import Rdioh.Models+import Data.String.Utils+import Control.Monad.IO.Class++key = strip . unsafePerformIO . readFile $ "key"+secret = strip . unsafePerformIO . readFile $ "secret"++main = do+    runRdio key secret $ do+      res1 <- search "Radiohead" "Artist" :: Rdio (Either String [Artist])+      case res1 of+        Left err -> liftIO $ putStrLn err+        Right (radiohead:_) -> do+          res2 <- getAlbumsForArtist (artistKey radiohead)+          case res2 of+            Left err -> liftIO $ putStrLn err+            Right albums -> liftIO $ mapM_ (putStrLn . albumName) albums
− src/RdioResult.hs
@@ -1,46 +0,0 @@-module RdioResult where-import qualified Text.JSON as J-import qualified Data.List as L---- functions to convert a JSValue to a RdioResult:---- this one converts all JSONObjects to nice tuples that can be used to make an RDioDict-conv x = RdioDict $ map (\(str, jsval) -> (str, clean jsval) ) $ J.fromJSObject x---- helper methods to make all values into RdioResult values--- so they can be stuffed into an RDioDict-clean (J.JSObject x) = conv x-clean (J.JSString x) = RdioString $ J.fromJSString x-clean (J.JSBool x) = RdioBool x-clean (J.JSRational _ x) = RdioRational x-clean (J.JSArray xs) = RdioArray $ map clean xs-clean J.JSNull = RdioNull--data RdioResult = RdioDict {rdioDictValue :: [(String, RdioResult)]} | RdioArray {rdioArrayValue :: [RdioResult]} | RdioString {rdioStringValue :: String} | RdioRational {rdioRationalValue :: Rational} | RdioBool {rdioBoolValue :: Bool} | RdioNull--dropLast x l = take (L.genericLength l - x) l--instance Show RdioResult where-    show (RdioDict xs) = "{" ++ (dropLast 2 (foldl (\acc (str, obj) -> acc ++ "\"" ++ str ++ "\"" ++ " : " ++ (show obj) ++ ", ") "" xs)) ++ "}"-    show (RdioArray xs) = "[" ++ (dropLast 2 (foldl (\acc x -> acc ++ (show x) ++ ", ") "" xs)) ++ "]"-    show (RdioString x) = "\"" ++ x ++ "\""-    show (RdioBool x) = show x-    show (RdioRational x) = show x-    show RdioNull = ""---instance J.JSON RdioResult where-    showJSON (RdioDict xs) = J.showJSONs xs-    showJSON (RdioString x) = J.showJSON x-    showJSON (RdioRational x) = J.showJSON (show x)-    showJSON (RdioBool x) = J.showJSON (show x)-    showJSON (RdioArray xs) = J.showJSONs xs-    showJSON RdioNull = J.showJSON ""-    readJSON (J.JSObject obj) = J.Ok $ conv obj---- helper methods for accessing parts of our RdioResult object-(RdioDict xs) ! str = snd . head $ filter (\(key, obj) -> key == str) xs-(RdioArray xs) .! num = xs !! num--keys (RdioDict xs) = map fst xs-values (RdioDict xs) = map snd xs
src/Rdioh.hs view
@@ -1,224 +1,453 @@ {-# LANGUAGE FlexibleContexts #-} -module Rdioh (-twoLegToken, threeLegToken,-(!), (.!), keys, values,-RdioScope(..), RdioSort(..), RdioObjectType(..), RdioTime(..), RdioResultType(..), RdioType(..), RdioCollaborationMode(..), RdioResult(..),-get, getObjectFromShortCode, getObjectFromUrl,-getAlbumsByUPC, getAlbumsForArtist, getTracksByISRC, getTracksForArtist, search, searchSuggestions,-addToCollection, getAlbumsForArtistInCollection, getAlbumsInCollection, getArtistsInCollection, getTracksForAlbumInCollection, getTracksForArtistInCollection, getTracksInCollection, removeFromCollection, setAvailableOffline,-addToPlaylist, createPlaylist, deletePlaylist, getPlaylists, removeFromPlaylist, setPlaylistCollaborating, setPlaylistCollaborationMode, setPlaylistFields, setPlaylistOrder,-addFriend, currentUser, findUserByEmail, findUserByVanityName, removeFriend, userFollowers, userFollowing,-getActivityStream, getHeavyRotation, getNewReleases, getTopCharts,-getPlaybackToken-) where+module Rdioh where+import Rdioh.Auth+import Data.Either +import qualified Data.ByteString.Lazy.Char8 as B+import qualified Data.List.Utils as U+import Control.Monad.Reader -import Data.Maybe  import Network.OAuth.Consumer import Network.OAuth.Http.Request import Network.OAuth.Http.Response import Network.OAuth.Http.HttpClient import Network.OAuth.Http.CurlHttpClient-import Network.OAuth.Http.PercentEncoding-import qualified Data.ByteString.Lazy.Char8 as B-import qualified Data.URLEncoded as UE-import qualified Data.List.Utils as U-import Control.Monad.Reader-import qualified Text.JSON as J-import RdioResult+-- import Network.OAuth.Http.PercentEncoding+import Rdioh.Util+import Rdioh.Models+import Control.Applicative+import Data.Aeson+import qualified Debug.Trace as D +-- | Takes: a key, a secret, a function to run.+runRdio :: String -> String -> Rdio a -> IO a+runRdio key secret func = runReaderT func (twoLegToken key secret) -reqUrl = fromJust . parseURL $ "http://api.rdio.com/oauth/request_token"-accUrl = fromJust . parseURL $ "http://api.rdio.com/oauth/access_token"-authUrl = ("https://www.rdio.com/oauth/authorize?oauth_token="++) . findWithDefault ("oauth_token","ERROR") . oauthParams-srvUrl payload = (fromJust . parseURL $ "http://api.rdio.com/1/") { method = POST-                                                          , reqPayload = payload-                                                          , reqHeaders = fromList [("content-type", "application/x-www-form-urlencoded")]-                                                          }+-- | Same as @runRdio@, but with 3-legged authentication i.e. the user will+-- | have to authorize your app.+runRdioWithAuth :: String -> String -> Rdio a -> IO a+runRdioWithAuth key secret func = do+    tok <- liftIO (threeLegToken key secret)+    runReaderT func tok -app key secret = Application key secret OOB+-- | The @Rdio@ monad...just a wrapper around a @ReaderT@ monad.+type Rdio a = ReaderT Token IO a --- returns a two-legged auth token-twoLegToken key secret   = fromApplication (app key secret)+-- | used internally+mkExtras :: Show e => [e] -> (String, String)+mkExtras extras = ("extras", U.join "," $ show <$> extras) --- given a key and a secret, does three-legged auth and returns an auth token-threeLegToken key secret = runOAuthM (twoLegToken key secret) $ do-    signRq2 HMACSHA1 Nothing reqUrl >>= oauthRequest CurlClient-    cliAskAuthorization authUrl-    signRq2 HMACSHA1 Nothing accUrl >>= oauthRequest CurlClient+-- | Send a arbitrary request to rdio's api. Return type should+-- | be an instance of @FromJSON@, and you need to specify the type. Example:+--+-- > result <- (runRequest [("method", "getTopCharts"), ("type", "Artist")] :: Rdio (Either String [Artist]))+runRequest :: (Show v, FromJSON v) => [(String, String)] -> Rdio (Either String v)+runRequest params = do+    tok <- ask+    let request = srvUrl . B.pack . toParams $ params+    response <- liftIO $ runOAuthM tok $ do+                      request_ <- signRq2 HMACSHA1 Nothing request+                      serviceRequest CurlClient request_ --- convert a list of parameters to a string that can be passed via GET/POST-toParams :: [(String, String)] -> String-toParams = show . UE.importList+    -- D.trace (B.unpack . rspPayload $ response) (return ())+    let value = eitherDecode . rspPayload $ response+    return $ rdioResult <$> value --- convert JSON str to parsed-toJSON str = fromResult $ (J.decode str :: J.Result (RdioResult))+-- RDIO methods --- given a list of tuples, return a string that's a JSON object:-jsonify_tuples t = (++"}") . init $ foldl (\acc (k, v) -> acc ++ k ++ ":" ++ v ++ ",") "{" t+-- CORE methods TODO they all have multiple return types. -fromResult (J.Ok x) = x-fromResult (J.Error x) = error x+-- TODO currently unsupported because it has a variable specification.+-- All you can really do is request an Object back. --- if the first parameter is a Just, then return the second parameter--- otherwise return an empty list-addMaybe (Just a) b = b-addMaybe Nothing b = []+-- | Takes: [keys], [extras] (optional)+get :: (Show a, FromJSON a) => [String] -> [String] -> Rdio (Either String a)+get keys extras = runRequest $ [("method", "get"), ("keys", toParam keys), ("extras", toParam extras)] --- needed b/c haskell capitalizes the first letter otherwise-bool_to_s True = "true"-bool_to_s False = "false"+-- | Takes: short code (everything after the http://rd.io/x/), [extras]+-- (optional)+getObjectFromShortCode :: (Show a, FromJSON a) => String -> [String] -> Rdio (Either String a)+getObjectFromShortCode shortCode extras = runRequest $ [("method", "getObjectFromShortCode"), ("short_code", shortCode), ("extras", toParam extras)] --- uses the Reader monad to get a token. Then uses that token--- to make a request to the service url. The returned response--- is parsed through extractResponse to return the result parsed--- from JSON to something else.-runRequest params = do-    tok <- ask    -    liftM extractResponse $ runOAuthM tok $ signRq2 HMACSHA1 Nothing (srvUrl (B.pack . toParams $ params)) >>= serviceRequest CurlClient+-- | Takes: url (everything after http://rdio.com/), [extras] (optional)+getObjectFromUrl :: (Show a, FromJSON a) => String -> [String] -> Rdio (Either String a)+getObjectFromUrl url extras = runRequest $ [("method", "getObjectFromUrl"), ("url", url), ("extras", toParam extras)] --- extracts just the response from whatever rdio returned-extractResponse = toJSON . B.unpack . rspPayload+-- CATALOG methods --- ADTs for RDIO+-- | Takes: a UPC code, [extras] (optional)+getAlbumsByUPC :: Int -> [AlbumExtra] -> Rdio (Either String [Album])+getAlbumsByUPC upc extras = runRequest $ [("method", "getAlbumsByUPC"), ("upc", toParam upc), mkExtras extras] -data RdioScope = USER_SCOPE | FRIENDS_SCOPE | EVERYONE_SCOPE-data RdioSort = DATE_ADDED_SORT | PLAY_COUNT_SORT | ARTIST_SORT | NAME_SORT-data RdioObjectType = ARTISTS_OBJECT_TYPE | ALBUMS_OBJECT_TYPE-data RdioTime = THIS_WEEK_TIME | LAST_WEEK_TIME | TWO_WEEKS_TIME-data RdioResultType = ARTIST_RESULT_TYPE | ALBUM_RESULT_TYPE | TRACK_RESULT_TYPE | PLAYLIST_RESULT_TYPE-data RdioType = ARTIST_TYPE | ALBUM_TYPE | TRACK_TYPE | PLAYLIST_TYPE | USER_TYPE-data RdioCollaborationMode = NO_COLLABORATION | COLLABORATION_WITH_ALL | COLLABORATION_WITH_FOLLOWED+-- | Takes: A key of an artist+getAlbumsForArtist :: String -> Rdio (Either String [Album])+getAlbumsForArtist artist = getAlbumsForArtist' artist Nothing [] Nothing Nothing --- RDIO methods+getAlbumsForArtist' :: String -> Maybe Bool -> [AlbumExtra] -> Maybe Int -> Maybe Int -> Rdio (Either String [Album])+getAlbumsForArtist' artist featuring extras start count =+    runRequest $ [("method", "getAlbumsForArtist"), ("artist", artist), mkExtras extras]+                   <+> ("featuring", featuring)+                   <+> ("start", start)+                   <+> ("count", count) --- CORE methods-get keys extras options = runRequest $ [("method", "get"), ("keys", U.join "," keys)] ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)]) ++ (addMaybe options [("options", jsonify_tuples $ fromJust options)])+-- | Takes: a key of a label+getAlbumsForLabel :: String -> Rdio (Either String [Album])+getAlbumsForLabel label = getAlbumsForLabel' label [] Nothing Nothing -getObjectFromShortCode short_code extras = runRequest $ [("method", "getObjectFromShortCode"), ("short_code", short_code)] ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+getAlbumsForLabel' :: String -> [AlbumExtra] -> Maybe Int -> Maybe Int -> Rdio (Either String [Album])+getAlbumsForLabel' label extras start count = +    runRequest $ [("method", "getAlbumsForLabel"), ("label", label), mkExtras extras]+                   <+> ("start", start)+                   <+> ("count", count) -getObjectFromUrl url extras = runRequest $ [("method", "getObjectFromUrl"), ("url", url)] ++  (addMaybe extras [("extras", U.join "," $ fromJust extras)])+-- | Takes: a key of a label+getArtistsForLabel :: String -> Rdio (Either String [Artist])+getArtistsForLabel label = getArtistsForLabel' label [] Nothing Nothing --- CATALOG methods-getAlbumsByUPC upc extras = runRequest $ [("method", "getAlbumsByUPC"), ("upc", upc)] ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+getArtistsForLabel' :: String -> [ArtistExtra] -> Maybe Int -> Maybe Int -> Rdio (Either String [Artist])+getArtistsForLabel' label extras start count = +    runRequest $ [("method", "getArtistsForLabel"), ("label", label), mkExtras extras]+                   <+> ("start", start)+                   <+> ("count", count) -getAlbumsForArtist artist featuring extras start count = runRequest $ [("method", "getAlbumsForArtist"), ("artist", artist)] ++ (addMaybe featuring [("featuring", (bool_to_s $ fromJust featuring))]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)]) ++ (addMaybe start [("start", show $ fromJust start)]) ++ (addMaybe count [("count", show $ fromJust count)])+-- | Takes: an ISRC code, [extras] (optional)+getTracksByISRC :: String -> [TrackExtra] -> Rdio (Either String [Track])+getTracksByISRC isrc extras = runRequest $ [("method", "getTracksByISRC"), ("isrc", isrc), mkExtras extras] -getTracksByISRC isrc extras = runRequest $ [("method", "getTracksByISRC"), ("isrc", isrc)] ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+-- | Takes: an artist key+getTracksForArtist :: String -> Rdio (Either String [Track])+getTracksForArtist artist = getTracksForArtist' artist Nothing [] Nothing Nothing -getTracksForArtist artist appears_on start count extras = runRequest $ [("method", "getTracksForArtist"), ("artist", artist)] ++ (addMaybe appears_on [("appears_on", bool_to_s $ fromJust appears_on)]) ++ (addMaybe start [("start", show $ fromJust start)]) ++ (addMaybe count [("count", show $ fromJust count)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+getTracksForArtist' :: String -> Maybe Bool -> [TrackExtra] -> Maybe Int -> Maybe Int -> Rdio (Either String [Track])+getTracksForArtist' artist appears_on extras start count =+    runRequest $ [("method", "getTracksForArtist"), ("artist", artist), mkExtras extras]+                    <+> ("appears_on", appears_on)+                    <+> ("start", start)+                    <+> ("count", count) -search query types never_or extras start count = runRequest $ [("method", "search"), ("query", query), ("types", U.join "," (map pretty types))] ++ (addMaybe never_or [("never_or", bool_to_s $ fromJust never_or)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)]) ++ (addMaybe start [("start", show $ fromJust start)]) ++ (addMaybe count [("count", show $ fromJust count)])-    where pretty ARTIST_TYPE = "Artist"-          pretty ALBUM_TYPE = "Album"-          pretty TRACK_TYPE = "Track"-          pretty PLAYLIST_TYPE = "Playlist"-          pretty USER_TYPE = "User"+-- TODO this should also have an extras field but how to implement that and+-- still be able to return a generic type?+-- | Takes: a query, a type (\"Artist\", \"Album\", \"Track\", \"Playlist\", or+-- \"User\")+-- This method can return any of those types, so you need to specify what+-- you want returned. Example:+--+-- > search "Radiohead" "Artist" :: Rdio (Either String [Artist])+search :: (Show a, FromJSON a) => String -> String -> Rdio (Either String [a])+search query types = search' query types Nothing Nothing Nothing -searchSuggestions query extras = runRequest $ [("method", "searchSuggestions"), ("query", query)] ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+search' :: (Show a, FromJSON a) => String -> String -> Maybe Bool -> Maybe Int -> Maybe Int -> Rdio (Either String [a])+search' query types neverOr start count = do+    res <- runRequest $ [("method", "search"), ("query", query), ("types", types)]+                   <+> ("never_or", neverOr)+                   <+> ("start", start)+                   <+> ("count", count) --- COLLECTION methods-addToCollection keys = runRequest [("method", "addToCollection"), ("keys", U.join "," keys)]+    return (results <$> res) -getAlbumsForArtistInCollection artist user extras = runRequest $ [("method", "getAlbumsForArtistInCollection"),("artist", artist)] ++ (addMaybe user [("user", fromJust user)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+-- TODO searchSuggestions -getAlbumsInCollection user start count sort query extras = runRequest $ [("method", "getAlbumsInCollection")] ++ (addMaybe user [("user", fromJust user)]) ++ (addMaybe start [("start", show $ fromJust start)]) ++ (addMaybe count [("count", show $ fromJust count)]) ++ (addMaybe sort [("sort", pretty $ fromJust sort)]) ++ (addMaybe query [("query", fromJust query)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])-    where pretty DATE_ADDED_SORT = "dateAdded"-          pretty PLAY_COUNT_SORT = "playCount"-          pretty ARTIST_SORT = "artist"-          pretty NAME_SORT = "name"+-- | Takes: a list of keys of tracks or playlists. *Requires+-- authentication*.+addToCollection :: [String] -> Rdio (Either String Object)+addToCollection keys = runRequest $ [("method", "addToCollection"), ("keys", toParam keys)] -getArtistsInCollection :: (MonadReader Token m, MonadIO m) => Maybe String -> Maybe Int -> Maybe Int -> Maybe RdioSort -> Maybe String -> Maybe [String] -> m (RdioResult)-getArtistsInCollection user start count sort query extras = runRequest $ [("method", "getArtistsInCollection")] ++ (addMaybe user [("user", fromJust user)]) ++ (addMaybe start [("start", show $ fromJust start)]) ++ (addMaybe count [("count", show $ fromJust count)]) ++ (addMaybe sort [("sort", pretty $ fromJust sort)]) ++ (addMaybe query [("query", fromJust query)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])-    where pretty DATE_ADDED_SORT = "dateAdded"-          pretty PLAY_COUNT_SORT = "playCount"-          pretty ARTIST_SORT = "artist"-          pretty NAME_SORT = "name"+-- | Takes: an artist key. Requires authentication OR use @getAlbumsForArtistInCollection'@ and pass in a user key.+getAlbumsForArtistInCollection :: String -> Rdio (Either String [Album])+getAlbumsForArtistInCollection artist = getAlbumsForArtistInCollection' artist Nothing [] Nothing -getTracksForAlbumInCollection album user extras = runRequest $ [("method", "getTracksForAlbumInCollection"), ("album", album)] ++ (addMaybe user [("user", fromJust user)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+getAlbumsForArtistInCollection' :: String -> Maybe String -> [AlbumExtra] -> Maybe String -> Rdio (Either String [Album])+getAlbumsForArtistInCollection' artist user extras sort = +    runRequest $ [("method", "getAlbumsForArtistInCollection"), ("artist", artist), mkExtras extras]+                  <+> ("user", user)+                  <+> ("sort", sort) -getTracksForArtistInCollection artist user extras = runRequest $ [("method", "getTracksForArtistInCollection"), ("artist", artist)] ++ (addMaybe user [("user", fromJust user)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+-- | Requires authentication OR use @getAlbumsInCollection'@ and pass in+-- a user key.+getAlbumsInCollection :: Rdio (Either String [Album])+getAlbumsInCollection = getAlbumsInCollection' Nothing Nothing Nothing Nothing Nothing [] -getTracksInCollection :: (MonadReader Token m, MonadIO m) => Maybe String -> Maybe Int -> Maybe Int -> Maybe RdioSort -> Maybe String -> Maybe [String] -> m (RdioResult)-getTracksInCollection user start count sort query extras = runRequest $ [("method", "getTracksInCollection")] ++ (addMaybe user [("user", fromJust user)]) ++ (addMaybe start [("start", show $ fromJust start)]) ++ (addMaybe count [("count", show $ fromJust count)]) ++ (addMaybe sort [("sort", pretty $ fromJust sort)]) ++ (addMaybe query [("query", fromJust query)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])-    where pretty DATE_ADDED_SORT = "dateAdded"-          pretty PLAY_COUNT_SORT = "playCount"-          pretty ARTIST_SORT = "artist"-          pretty NAME_SORT = "name"+getAlbumsInCollection' :: Maybe String -> Maybe Int -> Maybe Int -> Maybe String -> Maybe String -> [AlbumExtra] -> Rdio (Either String [Album])+getAlbumsInCollection' user start count sort query extras = +    runRequest $ [("method", "getAlbumsInCollection"), mkExtras extras]+                  <+> ("user", user)+                  <+> ("start", start)+                  <+> ("count", count)+                  <+> ("sort", sort)+                  <+> ("query", query) -removeFromCollection keys = runRequest [("method", "removeFromCollection"), ("keys", U.join "," keys)]+-- | Requires authentication OR use @getArtistsInCollection'@ and pass in+-- a user key.+getArtistsInCollection :: Rdio (Either String [Artist])+getArtistsInCollection = getArtistsInCollection' Nothing Nothing Nothing Nothing Nothing [] -setAvailableOffline :: (MonadReader Token m, MonadIO m) => [[Char]] -> Bool -> m (RdioResult)-setAvailableOffline keys offline = runRequest [("method", "setAvailableOffline"), ("keys", U.join "," keys), ("offline", (bool_to_s offline))]+getArtistsInCollection' :: Maybe String -> Maybe Int -> Maybe Int -> Maybe String -> Maybe String -> [ArtistExtra] -> Rdio (Either String [Artist])+getArtistsInCollection' user start count sort query extras = +    runRequest $ [("method", "getArtistsInCollection"), mkExtras extras]+                  <+> ("user", user)+                  <+> ("start", start)+                  <+> ("count", count)+                  <+> ("sort", sort)+                  <+> ("query", query) --- PLAYLIST methods-addToPlaylist playlist tracks = runRequest [("method", "addToPlaylist"), ("playlist", playlist), ("tracks", U.join "," tracks)]+-- | Requires authentication.+getOfflineTracks :: Rdio (Either String [Track])+getOfflineTracks = getOfflineTracks' Nothing Nothing [] -createPlaylist name description tracks extras = runRequest $ [("method", "createPlaylist"), ("name", name), ("description", description), ("tracks", U.join "," tracks)] ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+getOfflineTracks' :: Maybe Int -> Maybe Int -> [TrackExtra] -> Rdio (Either String [Track])+getOfflineTracks' start count extras = +    runRequest $ [("method", "getOfflineTracks"), mkExtras extras]+                  <+> ("start", start)+                  <+> ("count", count) -deletePlaylist playlist = runRequest [("method", "deletePlaylist"), ("playlist", playlist)]+-- | Takes: an album key. Requires authentication OR use @getTracksForAlbumInCollection'@ and pass in a user key.+getTracksForAlbumInCollection :: String -> Rdio (Either String [Track])+getTracksForAlbumInCollection album = getTracksForAlbumInCollection' album Nothing [] -getPlaylists extras = runRequest $ [("method", "getPlaylists")] ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+getTracksForAlbumInCollection' :: String -> Maybe String -> [TrackExtra] -> Rdio (Either String [Track])+getTracksForAlbumInCollection' album user extras =+    runRequest $ [("method", "getTracksForAlbumInCollection"), ("album", album), mkExtras extras]+                  <+> ("user", user) -removeFromPlaylist :: (MonadReader Token m, MonadIO m) => String -> Int -> Int -> [[Char]] -> m (RdioResult)-removeFromPlaylist playlist index count tracks = runRequest [("method", "removeFromPlaylist"), ("playlist", playlist), ("index", (show index)), ("count", (show count)), ("tracks", U.join "," tracks)]+-- | Takes: an artist key. Requires authentication OR use @getTracksForArtistInCollection'@ and pass in a user key.+getTracksForArtistInCollection :: String -> Rdio (Either String [Track])+getTracksForArtistInCollection artist = getTracksForArtistInCollection' artist Nothing [] -setPlaylistCollaborating :: (MonadReader Token m, MonadIO m) => String -> Bool -> m (RdioResult)-setPlaylistCollaborating playlist collaborating = runRequest [("method", "setPlaylistCollaborating"), ("playlist", playlist), ("collaborating", (bool_to_s collaborating))]+getTracksForArtistInCollection' :: String -> Maybe String -> [TrackExtra] -> Rdio (Either String [Track])+getTracksForArtistInCollection' artist user extras =+    runRequest $ [("method", "getTracksForArtistInCollection"), ("artist", artist), mkExtras extras]+                  <+> ("user", user) -setPlaylistCollaborationMode :: (MonadReader Token m, MonadIO m) => String -> RdioCollaborationMode -> m (RdioResult)-setPlaylistCollaborationMode playlist mode = runRequest [("method", "setPlaylistCollaborationMode"), ("playlist", playlist), ("mode", (pretty mode))]-    where pretty NO_COLLABORATION = "0"-          pretty COLLABORATION_WITH_ALL = "1"-          pretty COLLABORATION_WITH_FOLLOWED = "2"+-- | Requires authentication OR use @getTracksInCollection'@ and pass in+-- a user key.+getTracksInCollection :: Rdio (Either String [Track])+getTracksInCollection = getTracksInCollection' Nothing Nothing Nothing Nothing Nothing [] -setPlaylistFields playlist name description = runRequest [("method", "setPlaylistFields"), ("playlist", playlist), ("name", name), ("description", description)]+getTracksInCollection' :: Maybe String -> Maybe Int -> Maybe Int -> Maybe String -> Maybe String -> [TrackExtra] -> Rdio (Either String [Track])+getTracksInCollection' user start count sort query extras = +    runRequest $ [("method", "getTracksInCollection"), mkExtras extras]+                   <+> ("user", user)+                   <+> ("start", start)+                   <+> ("count", count)+                   <+> ("sort", sort)+                   <+> ("query", query) -setPlaylistOrder playlist tracks = runRequest [("method", "setPlaylistOrder"), ("playlist", playlist), ("tracks", U.join "," tracks)]+-- | Takes: a list of track or playlist keys. Requires authentication.+removeFromCollection :: [String] -> Rdio (Either String Bool)+removeFromCollection keys = runRequest $ [("method", "removeFromCollection"), ("keys", toParam keys)] --- SOCIAL NETWORK methods-addFriend user = runRequest [("method", "addFriend"), ("user", user)]+-- | Takes: a list of track or playlist keys. Requires authentication.+setAvailableOffline :: [String] -> Bool -> Rdio (Either String Object)+setAvailableOffline keys offline = runRequest $ [("method", "setAvailableOffline"), ("keys", toParam keys), ("offline", toParam offline)] -currentUser extras = runRequest $ [("method", "currentUser")] ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+-- | Takes: a playlist key, a list of track keys to add to the playlist,+-- [extras] (optional).+-- Requires authentication.+addToPlaylist :: String -> [String] -> [PlaylistExtra] -> Rdio (Either String Playlist)+addToPlaylist playlist tracks extras = runRequest $ [("method", "addToPlaylist"), ("playlist", playlist), ("tracks", toParam tracks), mkExtras extras] -findUserByEmail email extras = runRequest $ [("method", "findUser"), ("email", email)] ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+-- | Takes: a name, a description, a list of track keys to start the+-- playlist with, [extras] (optional). Requires authentication.+createPlaylist :: String -> String -> [String] -> [PlaylistExtra] -> Rdio (Either String Playlist)+createPlaylist name description tracks extras =+    runRequest $ [("method", "createPlaylist"), ("name", name), ("description", description), ("tracks", toParam tracks), mkExtras extras] -findUserByVanityName vanityName extras = runRequest $ [("method", "findUser"), ("vanityName", vanityName)] ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+-- | Takes: a playlist key. Requires authentication.+deletePlaylist :: String -> Rdio (Either String Bool)+deletePlaylist playlist = runRequest $ [("method", "deletePlaylist"), ("playlist", playlist)] -removeFriend user = runRequest [("method", "removeFriend"), ("user", user)]+-- | Requires authentication OR use @getPlaylists'@ and pass in a user key.+getPlaylists :: Rdio (Either String UserPlaylists)+getPlaylists = getPlaylists' Nothing [] Nothing -userFollowers :: (MonadReader Token m, MonadIO m) => String -> Maybe Int -> Maybe Int -> Maybe [String] -> m (RdioResult)-userFollowers user start count extras = runRequest $ [("method", "userFollowers"), ("user", user)] ++ (addMaybe start [("start", show start)]) ++ (addMaybe count [("count", show $ fromJust count)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+getPlaylists' :: Maybe String -> [PlaylistExtra] -> Maybe Bool -> Rdio (Either String UserPlaylists)+getPlaylists' user extras orderedList = +    runRequest $ [("method", "getPlaylists"), mkExtras extras]+                   <+> ("user", user)+                   <+> ("ordered_list", orderedList) -userFollowing :: (MonadReader Token m, MonadIO m) => String -> Maybe Int -> Maybe Int -> Maybe [String] -> m (RdioResult)-userFollowing user start count extras = runRequest $ [("method", "userFollowing"), ("user", user)] ++ (addMaybe start [("start", show $ fromJust start)]) ++ (addMaybe count [("count", show $ fromJust count)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])+-- | Requires authentication OR use @getUserPlaylists'@ and pass in a user key.+getUserPlaylists :: String -> Rdio (Either String [Playlist])+getUserPlaylists user = getUserPlaylists' user Nothing Nothing Nothing Nothing [] --- ACTIVITY AND STATISTICS methods+getUserPlaylists' :: String -> Maybe PlaylistType -> Maybe String -> Maybe Int -> Maybe Int -> [PlaylistExtra] -> Rdio (Either String [Playlist])+getUserPlaylists' user kind sort start count extras =+    runRequest $ [("method", "getUserPlaylists"), ("user", user), mkExtras extras]+                   <+> ("kind", kind)+                   <+> ("sort", sort)+                   <+> ("start", start)+                   <+> ("count", count) -getActivityStream user scope last_id extras = runRequest $ [("method", "getActivityStream"), ("scope", (pretty scope))] ++ (addMaybe last_id [("last_id", fromJust last_id)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])-    where pretty USER_SCOPE = "user"-          pretty FRIENDS_SCOPE = "friends"-          pretty EVERYONE_SCOPE = "everyone"+-- | Takes:+-- - a playlist key+--+-- - the index of the first item to remove+--+-- - number of tracks to remove+--+-- - the keys of the tracks to remove (redundancy to prevent accidental+-- deletion)+--+-- - [extras] (optional)+--+-- Requires authentication.+removeFromPlaylist :: String -> Int -> Int -> Int -> [PlaylistExtra] -> Rdio (Either String Playlist)+removeFromPlaylist playlist index count tracks extras =+    runRequest $ [("method", "removeFromPlaylist"), ("playlist", playlist), ("index", toParam index), ("count", toParam count), ("tracks", toParam tracks), mkExtras extras] -getHeavyRotation :: (MonadReader Token m, MonadIO m) => Maybe String -> Maybe RdioObjectType -> Maybe Bool -> Maybe Int -> Maybe Int -> Maybe Int -> Maybe [String] -> m (RdioResult)-getHeavyRotation user object_type friends limit start count extras = runRequest $ [("method", "getHeavyRotation")] ++ (addMaybe user [("user", fromJust user)]) ++ (addMaybe object_type [("object_type", pretty $ fromJust object_type)]) ++ (addMaybe friends [("friends", bool_to_s $ fromJust friends)]) ++ (addMaybe limit [("limit", show $ fromJust limit)]) ++ (addMaybe start [("start", show $ fromJust start)]) ++ (addMaybe count [("count", show $ fromJust count)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])-    where pretty ARTISTS_OBJECT_TYPE = "artist"-          pretty ALBUMS_OBJECT_TYPE = "albums"+-- | Takes: a playlist key, a boolean (true == collaborating, false == not+-- collaborating). Requires authentication.+setPlaylistCollaborating :: String -> Bool -> Rdio (Either String Bool)+setPlaylistCollaborating playlist collaborating =+    runRequest $ [("method", "setPlaylistCollaborating"), ("playlist", playlist), ("collaborating", toParam collaborating)] -getNewReleases :: (MonadReader Token m, MonadIO m) => Maybe RdioTime -> Maybe Int -> Maybe Int -> Maybe [String] -> m (RdioResult)-getNewReleases time start count extras = runRequest $ [("method", "getNewReleases")] ++ (addMaybe time [("time", pretty $ fromJust time)]) ++ (addMaybe start [("start", show $ fromJust start)]) ++ (addMaybe count [("count", show $ fromJust count)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])-    where pretty THIS_WEEK_TIME = "thisweek"-          pretty LAST_WEEK_TIME = "lastweek"-          pretty TWO_WEEKS_TIME = "twoweeks"+-- | Takes: a playlist key, a collaboration mode. Requires authentication.+setPlaylistCollaborationMode :: String -> CollaborationMode -> Rdio (Either String Bool)+setPlaylistCollaborationMode playlist mode =+    runRequest $ [("method", "setPlaylistCollaborationMode"), ("playlist", playlist), ("mode", toParam mode)] -getTopCharts :: (MonadReader Token m, MonadIO m) => RdioResultType -> Maybe Int -> Maybe Int -> Maybe [String] -> m (RdioResult)-getTopCharts result_type start count extras = runRequest $ [("method", "getTopCharts"), ("result_type", (pretty result_type))] ++ (addMaybe start [("start", show $ fromJust start)]) ++ (addMaybe count [("count", show $ fromJust count)]) ++ (addMaybe extras [("extras", U.join "," $ fromJust extras)])-    where pretty ARTIST_RESULT_TYPE = "Artist"-          pretty ALBUM_RESULT_TYPE = "Album"-          pretty TRACK_RESULT_TYPE = "Track"-          pretty PLAYLIST_RESULT_TYPE = "Playlist"+-- | Takes: a playlist key, a name, a description. Requires authentication.+setPlaylistFields :: String -> String -> String -> Rdio (Either String Bool)+setPlaylistFields playlist name description =+    runRequest $ [("method", "setPlaylistFields"), ("playlist", playlist), ("name", name), ("description", description)] --- PLAYBACK methods-getPlaybackToken domain = runRequest $ [("method", "getPlaybackToken")] ++ (addMaybe domain [("domain", fromJust domain)])+-- | Takes: a playlist key, a list of track keys, [extras] (optional).+-- Requires authentication.+setPlaylistOrder :: String -> [String] -> [PlaylistExtra] -> Rdio (Either String Playlist)+setPlaylistOrder playlist tracks extras =+    runRequest $ [("method", "setPlaylistOrder"), ("playlist", playlist), ("tracks", toParam tracks), mkExtras extras] +-- | Takes: a user key. Requires authentication.+addFriend :: String -> Rdio (Either String Bool)+addFriend user = runRequest $ [("method", "addFriend"), ("user", user)] +-- | Requires authentication.+currentUser :: [UserExtra] -> Rdio (Either String User)+currentUser extras = runRequest $ [("method", "currentUser"), mkExtras extras]++-- | Takes: an email address, [extras] (optional).+findUserByEmail :: String -> [UserExtra] -> Rdio (Either String User)+findUserByEmail email extras = runRequest $ [("method", "findUser"), ("email", email), mkExtras extras]++-- | Takes: user name, [extras] (optional).+findUserByName :: String -> [UserExtra] -> Rdio (Either String User)+findUserByName vanityName extras = runRequest $ [("method", "findUser"), ("vanityName", vanityName), mkExtras extras]++-- | Takes: a user key. Requires authentication.+removeFriend :: String -> Rdio (Either String Bool)+removeFriend user = runRequest $ [("method", "removeFriend"), ("user", user)]++-- | Takes: a user key.+userFollowers :: String -> Rdio (Either String [User])+userFollowers user = userFollowers' user Nothing Nothing [] Nothing++userFollowers' :: String -> Maybe Int -> Maybe Int -> [UserExtra] -> Maybe String -> Rdio (Either String [User])+userFollowers' user start count extras inCommon =+    runRequest $ [("method", "userFollowers"), ("user", user), mkExtras extras]+                   <+> ("start", start)+                   <+> ("count", count)+                   <+> ("inCommon", inCommon)++-- | Takes: a user key.+userFollowing :: String -> Rdio (Either String [User])+userFollowing user = userFollowing' user Nothing Nothing [] Nothing++userFollowing' :: String -> Maybe Int -> Maybe Int -> [UserExtra] -> Maybe String -> Rdio (Either String [User])+userFollowing' user start count extras inCommon =+    runRequest $ [("method", "userFollowing"), ("user", user), mkExtras extras]+                   <+> ("start", start)+                   <+> ("count", count)+                   <+> ("inCommon", inCommon)+++-- TODO extras not implemented here because I don't know what it means in+-- this context. Also, the docs say there will be additional data depending+-- on the update_type. Without specifying crap. So this is incomplete for+-- now.++-- | Takes: a user key, a scope.+getActivityStream :: String -> Scope -> Rdio (Either String Activity)+getActivityStream user scope = getActivityStream' user scope Nothing Nothing++getActivityStream' :: String -> Scope -> Maybe Int -> Maybe Int -> Rdio (Either String Activity)+getActivityStream' user scope lastId count = +    runRequest $ [("method", "getActivityStream"), ("user", user), ("scope", toParam scope)]+                   <+> ("last_id", lastId)+                   <+> ("count", count)++getHeavyRotationArtists :: Rdio (Either String [Artist])+getHeavyRotationArtists = getHeavyRotationArtists' Nothing Nothing Nothing Nothing Nothing []++getHeavyRotationArtists' :: Maybe String -> Maybe Bool -> Maybe Int -> Maybe Int -> Maybe Int -> [ArtistExtra] -> Rdio (Either String [Artist])+getHeavyRotationArtists' user friends limit start count extras =+    runRequest $ [("method", "getHeavyRotation"), ("type", "artists"), mkExtras extras]+                   <+> ("user", user)+                   <+> ("friends", friends)+                   <+> ("limit", limit)+                   <+> ("start", start)+                   <+> ("count", count)++getHeavyRotationAlbums :: Rdio (Either String [Album])+getHeavyRotationAlbums = getHeavyRotationAlbums' Nothing Nothing Nothing Nothing Nothing []++getHeavyRotationAlbums' :: Maybe String -> Maybe Bool -> Maybe Int -> Maybe Int -> Maybe Int -> [AlbumExtra] -> Rdio (Either String [Album])+getHeavyRotationAlbums' user friends limit start count extras =+    runRequest $ [("method", "getHeavyRotation"), ("type", "artists"), mkExtras extras]+                   <+> ("user", user)+                   <+> ("friends", friends)+                   <+> ("limit", limit)+                   <+> ("start", start)+                   <+> ("count", count)++getNewReleases :: Rdio (Either String [Album])+getNewReleases = getNewReleases' Nothing Nothing Nothing []++getNewReleases' :: Maybe Timeframe -> Maybe Int -> Maybe Int -> [AlbumExtra] -> Rdio (Either String [Album])+getNewReleases' time start count extras =+    runRequest $ [("method", "getNewReleases"), mkExtras extras]+                   <+> ("time", time)+                   <+> ("start", start)+                   <+> ("count", count)++getTopChartArtists :: Rdio (Either String [Artist])+getTopChartArtists = getTopChartArtists' Nothing Nothing []++getTopChartArtists' :: Maybe Int -> Maybe Int -> [ArtistExtra] -> Rdio (Either String [Artist])+getTopChartArtists' start count extras =+    runRequest $ [("method", "getTopCharts"), ("type", "Artist"), mkExtras extras]+                   <+> ("start", start)+                   <+> ("count", count)++getTopChartAlbums :: Rdio (Either String [Album])+getTopChartAlbums = getTopChartAlbums' Nothing Nothing []++getTopChartAlbums' :: Maybe Int -> Maybe Int -> [AlbumExtra] -> Rdio (Either String [Album])+getTopChartAlbums' start count extras =+    runRequest $ [("method", "getTopCharts"), ("type", "Album"), mkExtras extras]+                   <+> ("start", start)+                   <+> ("count", count)+++getTopChartTracks :: Rdio (Either String [Track])+getTopChartTracks = getTopChartTracks' Nothing Nothing []++getTopChartTracks' :: Maybe Int -> Maybe Int -> [TrackExtra] -> Rdio (Either String [Track])+getTopChartTracks' start count extras =+    runRequest $ [("method", "getTopCharts"), ("type", "Track"), mkExtras extras]+                   <+> ("start", start)+                   <+> ("count", count)+++getTopChartPlaylists :: Rdio (Either String [Playlist])+getTopChartPlaylists = getTopChartPlaylists' Nothing Nothing []++getTopChartPlaylists' :: Maybe Int -> Maybe Int -> [PlaylistExtra] -> Rdio (Either String [Playlist])+getTopChartPlaylists' start count extras =+    runRequest $ [("method", "getTopCharts"), ("type", "Playlist"), mkExtras extras]+                   <+> ("start", start)+                   <+> ("count", count)++-- | Takes: the domain that the playback SWF will be embedded in+-- (optional).+getPlaybackToken :: Maybe String -> Rdio (Either String String)+getPlaybackToken domain = runRequest $ [("method", "getPlaybackToken")] <+> ("domain", domain)
+ src/Rdioh/Auth.hs view
@@ -0,0 +1,32 @@+module Rdioh.Auth where+import Network.OAuth.Consumer+import Network.OAuth.Http.Request+import Network.OAuth.Http.Response+import Network.OAuth.Http.HttpClient+import Network.OAuth.Http.CurlHttpClient+import Network.OAuth.Http.PercentEncoding+import Data.Maybe+import Data.List+import Control.Monad.IO.Class++reqUrl = fromJust . parseURL $ "http://api.rdio.com/oauth/request_token"+accUrl = fromJust . parseURL $ "http://api.rdio.com/oauth/access_token"+authUrl = ("https://www.rdio.com/oauth/authorize?oauth_token="++) . findWithDefault ("oauth_token","ERROR") . oauthParams+srvUrl payload = (fromJust . parseURL $ "http://api.rdio.com/1/") { method = POST+                                                          , reqPayload = payload+                                                          , reqHeaders = fromList [("content-type", "application/x-www-form-urlencoded")]+                                                          }++app key secret = Application key secret OOB++-- | Takes: a key and a secret. Returns a two-legged auth token. You can+-- just use @runRdio@ in most cases.+twoLegToken :: String -> String -> Token+twoLegToken key secret = fromApplication (app key secret)++-- | Takes: a key and a secret. Does three-legged auth and returns an auth token. You can just use @runRdioWithAuth@ in most cases.+threeLegToken :: MonadIO m => String -> String -> m Token+threeLegToken key secret = runOAuthM (twoLegToken key secret) $ do+    signRq2 HMACSHA1 Nothing reqUrl >>= oauthRequest CurlClient+    cliAskAuthorization authUrl+    signRq2 HMACSHA1 Nothing accUrl >>= oauthRequest CurlClient
+ src/Rdioh/Models.hs view
@@ -0,0 +1,740 @@+{-# LANGUAGE OverloadedStrings #-}++{-+IDEA: all of these modles could have been split up into their own modules:+Rdioh.Model.Artist etc etc.+Then we wouldn't have these scoping issues and all the fields could be normal names.++Downside: user has to import each module explicitly to use it.+-}++module Rdioh.Models where+import Data.Aeson+import Control.Applicative++data AlbumExtra = AlbumIframeUrl | AlbumIsCompilation | AlbumLabel | AlbumBigIcon | AlbumReleaseDateISO++instance Show AlbumExtra where+    show AlbumIframeUrl = "iframeUrl"+    show AlbumIsCompilation = "isCompilation"+    show AlbumLabel = "label"+    show AlbumBigIcon = "bigIcon"+    show AlbumReleaseDateISO = "releaseDateISO"++data Album = Album {+               albumName :: String,+               albumIcon :: String,+               albumBaseIcon :: String,+               albumUrl :: String,+               albumArtist :: String,+               albumArtistUrl :: String,+               isExplicit :: Bool,+               isClean :: Bool,+               albumLength :: Int,+               albumArtistKey :: String,+               trackKeys :: [String],+               price :: String,+               canStream :: Bool,+               canSample :: Bool,+               canTether :: Bool,+               albumShortUrl :: String,+               embedUrl :: String,+               displayDate :: String,+               albumKey :: String,+               releaseDate :: String,+               duration :: Int,+               iframeUrl :: Maybe String,+               isCompilation :: Maybe Bool,+               albumLabel :: Maybe String,+               albumBigIcon :: Maybe String,+               releaseDateISO :: Maybe String+} deriving (Show)++instance FromJSON Album where+  parseJSON (Object v) = Album <$> v .: "name"+                               <*> v .: "icon"+                               <*> v .: "baseIcon"+                               <*> v .: "url"+                               <*> v .: "artist"+                               <*> v .: "artistUrl"+                               <*> v .: "isExplicit"+                               <*> v .: "isClean"+                               <*> v .: "length"+                               <*> v .: "artistKey"+                               <*> v .: "trackKeys"+                               <*> v .: "price"+                               <*> v .: "canStream"+                               <*> v .: "canSample"+                               <*> v .: "canTether"+                               <*> v .: "shortUrl"+                               <*> v .: "embedUrl"+                               <*> v .: "displayDate"+                               <*> v .: "key"+                               <*> v .: "releaseDate"+                               <*> v .: "duration"+                               <*> v .:? "iframeUrl"+                               <*> v .:? "isCompilation"+                               <*> v .:? "label"+                               <*> v .:? "bigIcon"+                               <*> v .:? "releaseDateISO"++data ArtistExtra = ArtistAlbumCount++instance Show ArtistExtra where+    show ArtistAlbumCount = "albumCount"++data Artist = Artist {+            artistName :: String,+            artistKey :: String,+            artistUrl :: String,+            artistLength :: Int,+            artistIcon :: String,+            artistBaseIcon :: String,+            hasRadio :: Bool,+            artistShortUrl :: String,+            radioKey :: Maybe String,+            topSongsKey :: Maybe String,+            artistAlbumCount :: Maybe Int+} deriving (Show)++instance FromJSON Artist where+  parseJSON (Object v) = Artist <$> v .: "name"+                                <*> v .: "key"+                                <*> v .: "url"+                                <*> v .: "length"+                                <*> v .: "icon"+                                <*> v .: "baseIcon"+                                <*> v .: "hasRadio"+                                <*> v .: "shortUrl"+                                <*> v .:? "radioKey"+                                <*> v .:? "topSongsKey"+                                <*> v .:? "albumCount"++data Label = Label {+           labelName :: String,+           labelKey :: String,+           labelUrl :: String,+           labelShortUrl :: String,+           labelHasRadio :: Bool,+           labelRadioKey :: String+} deriving (Show)++instance FromJSON Label where+  parseJSON (Object v) = Label <$> v .: "name"+                               <*> v .: "key"+                               <*> v .: "url"+                               <*> v .: "shortUrl"+                               <*> v .: "hasRadio"+                               <*> v .: "radioKey"++data TrackExtra = IsInCollection | IsOnCompilation | Isrcs | TrackIframeUrl | PlayCount | TrackBigIcon++instance Show TrackExtra where+    show IsInCollection = "isInCollection"+    show IsOnCompilation = "isOnCompilation"+    show Isrcs = "isrcs"+    show TrackIframeUrl = "iframeUrl"+    show PlayCount = "playCount"+    show TrackBigIcon = "bigIcon"++data Track = Track {+           trackName :: String,+           trackArtist :: String,+           trackAlbum :: String,+           trackAlbumKey :: String,+           trackAlbumUrl :: String,+           trackArtistKey :: String,+           trackArtistUrl :: String,+           trackDuration :: Int,+           trackIsExplicit :: Bool,+           trackIsClean :: Bool,+           trackUrl :: String,+           trackBaseIcon :: String,+           trackCanDownload :: Bool,+           trackCanDownloadAlbumOnly :: Bool,+           trackCanStream :: Bool,+           trackCanTether :: Bool,+           trackCanSample :: Bool,+           trackPrice :: String,+           trackEmbedUrl :: String,+           trackKey :: String,+           trackIcon :: String,+           trackNum :: Int,+           trackAlbumArtist :: Maybe String,+           trackAlbumArtistKey :: Maybe String,+           isInCollection :: Maybe Bool,+           isOnCompilation :: Maybe Bool,+           isrcs :: Maybe [String],+           trackIframeUrl :: Maybe String,+           playCount :: Maybe Int,+           trackBigIcon :: Maybe String+} deriving (Show)++instance FromJSON Track where+  parseJSON (Object v) = Track <$> v .: "name"+                               <*> v .: "artist"+                               <*> v .: "album"+                               <*> v .: "albumKey"+                               <*> v .: "albumUrl"+                               <*> v .: "artistKey"+                               <*> v .: "artistUrl"+                               <*> v .: "duration"+                               <*> v .: "isExplicit"+                               <*> v .: "isClean"+                               <*> v .: "url"+                               <*> v .: "baseIcon"+                               <*> v .: "canDownload"+                               <*> v .: "canDownloadAlbumOnly"+                               <*> v .: "canStream"+                               <*> v .: "canTether"+                               <*> v .: "canSample"+                               <*> v .: "price"+                               <*> v .: "embedUrl"+                               <*> v .: "key"+                               <*> v .: "icon"+                               <*> v .: "trackNum"+                               <*> v .:? "albumArtist"+                               <*> v .:? "albumArtistKey"+                               <*> v .:? "isInCollection"+                               <*> v .:? "isOnCompilation"+                               <*> v .:? "isrcs"+                               <*> v .:? "iframeUrl"+                               <*> v .:? "playCount"+                               <*> v .:? "bigIcon"++data Reason = Viewable | UserPreference | OrderedAlbum | TooFewSongs deriving (Show)++instance FromJSON Reason where+  parseJSON (Number n)+      | n == 0 = return Viewable+      | n == 1 = return UserPreference+      | n == 2 = return OrderedAlbum+      | n == 3 = return TooFewSongs++data PlaylistExtra = PlIframeUrl | IsViewable | PlBigIcon | PlDescription | PlTracks | IsPublished | PlTrackKeys | ReasonNotViewable++instance Show PlaylistExtra where+    show PlIframeUrl = "iframeUrl"+    show IsViewable = "isViewable"+    show PlBigIcon = "bigIcon"+    show PlDescription = "description"+    show PlTracks = "tracks"+    show IsPublished = "isPublished"+    show PlTrackKeys = "trackKeys"+    show ReasonNotViewable = "reasonNotViewable"++data Playlist = Playlist {+              plName :: String,+              plUrl :: String,+              plOwner :: String,+              plOwnerUrl :: String,+              plOwnerKey :: String,+              plOwnerIcon :: String,+              plShortUrl :: String,+              plEmbedUrl :: String,+              plKey :: String,+              plLength :: Maybe Int,+              plIcon :: Maybe String,+              plBaseIcon :: Maybe String,+              lastUpdated :: Maybe Int,+              plIFrameUrl :: Maybe String,+              isViewable :: Maybe Bool,+              plBigIcon :: Maybe String,+              plDescription :: Maybe String,+              plTracks :: Maybe [Track],+              isPublished :: Maybe Bool,+              plTrackKeys :: Maybe [String],+              reasonNotViewable :: Maybe Reason+} deriving (Show)++instance FromJSON Playlist where+  parseJSON (Object v) = Playlist <$> v .: "name"+                                  <*> v .: "url"+                                  <*> v .: "owner"+                                  <*> v .: "ownerUrl"+                                  <*> v .: "ownerKey"+                                  <*> v .: "ownerIcon"+                                  <*> v .: "shortUrl"+                                  <*> v .: "embedUrl"+                                  <*> v .: "key"+                                  <*> v .:? "length"+                                  <*> v .:? "icon"+                                  <*> v .:? "baseIcon"+                                  <*> v .:? "lastUpdated"+                                  <*> v .:? "iframeUrl"+                                  <*> v .:? "isViewable"+                                  <*> v .:? "bigIcon"+                                  <*> v .:? "description"+                                  <*> v .:? "tracks"+                                  <*> v .:? "isPublished"+                                  <*> v .:? "trackKeys"+                                  <*> v .:? "reasonNotViewable"++data Gender = Male | Female deriving (Show)++data UserPlaylists = UserPlaylists {+                   upOwned :: [Playlist],+                   upCollab :: [Playlist],+                   upSubscribed :: [Playlist]+} deriving (Show)++instance FromJSON UserPlaylists where+  parseJSON (Object v) = UserPlaylists <$> v .: "owned"+                                       <*> v .: "collab"+                                       <*> v .: "subscribed"+++instance FromJSON Gender where+  parseJSON (String s) = return (if s == "m" then Male else Female)++data User = User {+              userKey :: String,+              firstName :: String,+              lastName :: String,+              userIcon :: String,+              userBaseIcon :: String,+              libraryVersion :: Int,+              userUrl :: String,+              gender :: Gender,+              followingUrl :: Maybe String,+              isTrial :: Maybe Bool,+              artistCount :: Maybe Int,+              lastSongPlayed :: Maybe String,+              heavyRotationKey :: Maybe String,+              networkHeavyRotationKey :: Maybe String,+              albumCount :: Maybe Int,+              trackCount :: Maybe Int,+              lastSongPlayTime :: Maybe String,+              username :: Maybe String,+              reviewCount :: Maybe Int,+              collectionUrl :: Maybe String,+              playlistsUrl :: Maybe String,+              collectionKey :: Maybe String,+              followersUrl :: Maybe String,+              displayName :: Maybe String,+              isUnlimited :: Maybe Bool,+              isSubscriber :: Maybe Bool+} deriving (Show)++data UserExtra = FollowingUrl | IsTrial | ArtistCount | LastSongPlayed | HeavyRotationKey | NetworkHeavyRotationKey | AlbumCount | TrackCount | LastSongPlayTime | Username | ReviewCount | CollectionUrl | PlaylistsUrl | CollectionKey | FollowersUrl | DisplayName | IsUnlimited | IsSubscriber++instance Show UserExtra where+    show FollowingUrl = "followingUrl"+    show IsTrial = "isTrial"+    show ArtistCount = "artistCount"+    show LastSongPlayed = "lastSongPlayed"+    show HeavyRotationKey = "heavyRotationKey"+    show NetworkHeavyRotationKey = "networkHeavyRotationKey"+    show AlbumCount = "albumCount"+    show TrackCount = "trackCount"+    show LastSongPlayTime = "lastSongPlayTime"+    show Username = "username"+    show ReviewCount = "reviewCount"+    show CollectionUrl = "collectionUrl"+    show PlaylistsUrl = "playlistsUrl"+    show CollectionKey = "collectionkey"+    show FollowersUrl = "followersUrl"+    show DisplayName = "displayName"+    show IsUnlimited = "isUnlimited"+    show IsSubscriber = "isSubscriber"++instance FromJSON User where+  parseJSON (Object v) = User <$> v .: "key"+                              <*> v .: "firstName"+                              <*> v .: "lastName"+                              <*> v .: "icon"+                              <*> v .: "baseIcon"+                              <*> v .: "libraryVersion"+                              <*> v .: "url"+                              <*> v .: "gender"+                              <*> v .:? "followingUrl"+                              <*> v .:? "isTrial"+                              <*> v .:? "artistCount"+                              <*> v .:? "lastSongPlayed"+                              <*> v .:? "heavyRotationKey"+                              <*> v .:? "networkHeavyRotationKey"+                              <*> v .:? "albumCount"+                              <*> v .:? "trackCount"+                              <*> v .:? "lastSongPlayTime"+                              <*> v .:? "username"+                              <*> v .:? "reviewCount"+                              <*> v .:? "collectionUrl"+                              <*> v .:? "playlistsUrl"+                              <*> v .:? "collectionKey"+                              <*> v .:? "followersUrl"+                              <*> v .:? "displayName"+                              <*> v .:? "isUnlimited"+                              <*> v .:? "isSubscriber"++data CollectionAlbum = CollectionAlbum {+                          colName :: String,+                          colIcon :: String,+                          colBaseIcon :: String,+                          colUrl :: String,+                          colArtist :: String,+                          colAlbumArtistUrl :: String,+                          colIsExplicit :: Bool,+                          colIsClean :: Bool,+                          colLength :: Int,+                          colAlbumArtistKey :: String,+                          colTrackKeys :: [String],+                          colPrice :: String,+                          colCanStream :: Bool,+                          colCanSample :: Bool,+                          colCanTether :: Bool,+                          colShortUrl :: String,+                          colEmbedUrl :: String,+                          colDisplayDate :: String,+                          colKey :: String,+                          colReleaseDate :: String,+                          colDuration :: Int,+                          colUserKey :: String,+                          colUserName :: String,+                          colAlbumKey :: String,+                          colAlbumUrl :: String,+                          colCollectionUrl :: String,+                          colItemTrackKeys :: [String],+                          colIframeUrl :: Maybe String,+                          colUserGender :: Maybe Gender,+                          colIsCompilation :: Maybe Bool,+                          colLabel :: Maybe String,+                          colReleaseDateISO :: Maybe String,+                          colUpcs :: Maybe [String],+                          colBigIcon :: Maybe String+} deriving (Show)++instance FromJSON CollectionAlbum where+  parseJSON (Object v) = CollectionAlbum <$> v .: "name"+                                         <*> v .: "icon"+                                         <*> v .: "baseIcon"+                                         <*> v .: "url"+                                         <*> v .: "artist"+                                         <*> v .: "artistUrl"+                                         <*> v .: "isExplicit"+                                         <*> v .: "isClean"+                                         <*> v .: "length"+                                         <*> v .: "artistKey"+                                         <*> v .: "trackKeys"+                                         <*> v .: "price"+                                         <*> v .: "canStream"+                                         <*> v .: "canSample"+                                         <*> v .: "canTether"+                                         <*> v .: "shortUrl"+                                         <*> v .: "embedUrl"+                                         <*> v .: "displayDate"+                                         <*> v .: "key"+                                         <*> v .: "releaseDate"+                                         <*> v .: "duration"+                                         <*> v .: "userkey"+                                         <*> v .: "userName"+                                         <*> v .: "albumKey"+                                         <*> v .: "albumUrl"+                                         <*> v .: "collectionUrl"+                                         <*> v .: "itemTrackKeys"+                                         <*> v .:? "iframeUrl"+                                         <*> v .:? "userGender"+                                         <*> v .:? "isCompilation"+                                         <*> v .:? "label"+                                         <*> v .:? "releaseDateISO"+                                         <*> v .:? "upcs"+                                         <*> v .:? "bigIcon"++data CollectionArtist = CollectionArtist {+                          colArtistName :: String,+                          colArtistKey :: String,+                          colArtistUrl :: String,+                          colArtistLength :: Int,+                          colArtistIcon :: String,+                          colArtistBaseIcon :: String,+                          colArtistHasRadio :: Bool,+                          colArtistShortUrl :: String,+                          colArtistRadioKey :: String,+                          colArtistTopSongsKey :: [String],+                          colArtistUserKey :: String,+                          colArtistUserName :: String,+                          colArtistArtistKey :: String,+                          colArtistArtistUrl :: String,+                          colArtistCollectionUrl :: String,+                          colCount :: Maybe Int,+                          colAlbumCount :: Maybe Int+} deriving (Show)++instance FromJSON CollectionArtist where+  parseJSON (Object v) = CollectionArtist <$> v .: "name"+                                          <*> v .: "key"+                                          <*> v .: "url"+                                          <*> v .: "length"+                                          <*> v .: "icon"+                                          <*> v .: "baseIcon"+                                          <*> v .: "hasRadio"+                                          <*> v .: "shortUrl"+                                          <*> v .: "radioKey"+                                          <*> v .: "topSongsKey"+                                          <*> v .: "userKey"+                                          <*> v .: "userName"+                                          <*> v .: "artistKey"+                                          <*> v .: "artistUrl"+                                          <*> v .: "collectionUrl"+                                          <*> v .:? "count"+                                          <*> v .:? "albumCount"++data LabelStation = LabelStation {+                      lsCount :: Int,+                      lsLabelName :: String,+                      lsName :: String,+                      lsHasRadio :: Bool,+                      lsTracks :: [String],+                      lsLabelUrl :: String,+                      lsShortUrl :: String,+                      lsLength :: Int,+                      lsUrl :: String,+                      lsKey :: String,+                      lsRadioKey :: String,+                      lsReloadOnRepeat :: Bool,+                      lsTrackKeys :: Maybe [String]+} deriving (Show)++instance FromJSON LabelStation where+  parseJSON (Object v) = LabelStation <$> v .: "count"+                                      <*> v .: "labelName"+                                      <*> v .: "name"+                                      <*> v .: "hasRadio"+                                      <*> v .: "tracks"+                                      <*> v .: "labelUrl"+                                      <*> v .: "shortUrl"+                                      <*> v .: "length"+                                      <*> v .: "url"+                                      <*> v .: "key"+                                      <*> v .: "radioKey"+                                      <*> v .: "reloadOnRepeat"+                                      <*> v .:? "trackKeys"++data ArtistStation = ArtistStation {+                       asRadioKey :: String,+                       asTopSongsKey :: String,+                       asBaseIcon :: String,+                       asTracks :: [String],+                       asArtistUrl :: String,+                       asKey :: String,+                       asReloadOnRepeat :: Bool,+                       asIcon :: String,+                       asCount :: Int,+                       asName :: String,+                       asHasRadio :: Bool,+                       asUrl :: String,+                       asArtistName :: String,+                       asShortUrl :: String,+                       asLength :: Int,+                       asAlbumCount :: Maybe Int,+                       asTrackKeys :: Maybe [String]+} deriving (Show)++instance FromJSON ArtistStation where+  parseJSON (Object v) = ArtistStation <$> v .: "radioKey"+                                       <*> v .: "topSongsKey"+                                       <*> v .: "baseIcon"+                                       <*> v .: "tracks"+                                       <*> v .: "artistUrl"+                                       <*> v .: "key"+                                       <*> v .: "reloadOnRepeat"+                                       <*> v .: "icon"+                                       <*> v .: "count"+                                       <*> v .: "name"+                                       <*> v .: "hasRadio"+                                       <*> v .: "url"+                                       <*> v .: "artistName"+                                       <*> v .: "shortUrl"+                                       <*> v .: "length"+                                       <*> v .:? "albumCount"+                                       <*> v .:? "trackKeys"++data HeavyRotationStation = HeavyRotationStation {+                              hrsKey :: String,+                              hrsLength :: Int,+                              hrsTracks :: Int,+                              hrsReloadOnRepeat :: Bool,+                              hrsCount :: Int,+                              hrsUser :: String,+                              hrsBaseIcon :: String,+                              hrsIcon :: String,+                              hrsName :: String,+                              hrsTrackKeys :: Maybe [String]+} deriving (Show)++instance FromJSON HeavyRotationStation where+  parseJSON (Object v) = HeavyRotationStation <$> v .: "key"+                                              <*> v .: "length"+                                              <*> v .: "tracks"+                                              <*> v .: "reloadOnRepeat"+                                              <*> v .: "count"+                                              <*> v .: "user"+                                              <*> v .: "baseIcon"+                                              <*> v .: "icon"+                                              <*> v .: "name"+                                              <*> v .:? "trackKeys"++data HeavyRotationUserStation = HeavyRotationUserStation {+                                  hrusKey :: String,+                                  hrusLength :: Int,+                                  hrusTracks :: Int,+                                  hrusReloadOnRepeat :: Bool,+                                  hrusCount :: Int,+                                  hrusUser :: String,+                                  hrusBaseIcon :: String,+                                  hrusIcon :: String,+                                  hrusName :: String,+                                  hrusTrackKeys :: Maybe [String]+} deriving (Show)++instance FromJSON HeavyRotationUserStation where+  parseJSON (Object v) = HeavyRotationUserStation <$> v .: "key"+                                                  <*> v .: "length"+                                                  <*> v .: "tracks"+                                                  <*> v .: "reloadOnRepeat"+                                                  <*> v .: "count"+                                                  <*> v .: "user"+                                                  <*> v .: "baseIcon"+                                                  <*> v .: "icon"+                                                  <*> v .: "name"+                                                  <*> v .:? "trackKeys"++data ArtistTopSongsStation = ArtistTopSongsStation {+                               atssRadioKey :: String,+                               atssTopSongsKey :: String,+                               atssBaseIcon :: String,+                               atssTracks :: [String],+                               atssArtistUrl :: String,+                               atssKey :: String,+                               atssReloadOnRepeat :: Bool,+                               atssIcon :: String,+                               atssCount :: Int,+                               atssName :: String,+                               atssHasRadio :: Bool,+                               atssUrl :: String,+                               atssArtistName :: String,+                               atssShortUrl :: String,+                               atssLength :: Int,+                               atssAlbumCount :: Maybe Int,+                               atssTrackKeys :: Maybe [String]+} deriving (Show)++instance FromJSON ArtistTopSongsStation where+  parseJSON (Object v) = ArtistTopSongsStation <$> v .: "radioKey"+                                               <*> v .: "topSongsKey"+                                               <*> v .: "baseIcon"+                                               <*> v .: "tracks"+                                               <*> v .: "artistUrl"+                                               <*> v .: "key"+                                               <*> v .: "reloadOnRepeat"+                                               <*> v .: "icon"+                                               <*> v .: "count"+                                               <*> v .: "name"+                                               <*> v .: "hasRadio"+                                               <*> v .: "url"+                                               <*> v .: "artistName"+                                               <*> v .: "shortUrl"+                                               <*> v .: "length"+                                               <*> v .:? "albumCount"+                                               <*> v .:? "trackKeys"++data UserCollectionStation = UserCollectionStation {+                               ucsKey :: String,+                               ucsLength :: Int,+                               ucsTracks :: [String],+                               ucsReloadOnRepeat :: Bool,+                               ucsCount :: Int,+                               ucsUser :: String,+                               ucsBaseIcon :: String,+                               ucsIcon :: String,+                               ucsName :: String,+                               ucsUrl :: String,+                               ucsTrackKeys :: Maybe [String]+} deriving (Show)++instance FromJSON UserCollectionStation where+  parseJSON (Object v) = UserCollectionStation <$> v .: "key"+                                               <*> v .: "length"+                                               <*> v .: "tracks"+                                               <*> v .: "reloadOnRepeat"+                                               <*> v .: "count"+                                               <*> v .: "user"+                                               <*> v .: "baseIcon"+                                               <*> v .: "icon"+                                               <*> v .: "name"+                                               <*> v .: "url"+                                               <*> v .:? "trackKeys"++data RdioResponse v = RdioResponse {+                      rdioStatus :: String,+                      rdioResult :: v+} deriving (Show)++instance FromJSON a => FromJSON (RdioResponse a) where+  parseJSON (Object v) = RdioResponse <$> v .: "status"+                                      <*> v .: "result"++data SearchResults v = SearchResults {+                      results :: [v]+} deriving (Show)++instance FromJSON a => FromJSON (SearchResults a) where+  parseJSON (Object v) = SearchResults <$> v .: "results"++data PlaylistType = Owned | Collab | Subscribed++instance Show PlaylistType where+    show Owned = "owned"+    show Collab = "collab"+    show Subscribed = "subscribed"++data CollaborationMode = NoCollaboration | CollaborationWithAll | CollaborationWithFollowed deriving (Show)++data Scope = UserScope | FriendScope | AllScope++instance Show Scope where+    show UserScope = "user"+    show FriendScope = "friends"+    show AllScope = "everyone"++data Activity = Activity {+                  activityUser :: User,+                  updates :: [Update]+} deriving (Show)++instance FromJSON Activity where+  parseJSON (Object v) = Activity <$> v .: "user"+                                  <*> v .: "updates"++data UpdateType = UTrackAddedToCollection | UTrackAddedToPlaylist | UFriendAdded | UUserJoined | UCommentOnTrack | UCommentOnAlbum | UCommentOnArtist | UCommentOnPlaylist | UTrackAddedViaMatchCollection | UUserSubscribed | UTrackSynced deriving (Show)++instance FromJSON UpdateType where+  parseJSON (Number n)+      | n == 0 = return UTrackAddedToCollection+      | n == 1 = return UTrackAddedToPlaylist+      | n == 3 = return UFriendAdded+      | n == 5 = return UUserJoined+      | n == 6 = return UCommentOnTrack+      | n == 7 = return UCommentOnAlbum+      | n == 8 = return UCommentOnArtist+      | n == 9 = return UCommentOnPlaylist+      | n == 10 = return UTrackAddedViaMatchCollection+      | n == 11 = return UUserSubscribed+      | n == 12 = return UTrackSynced+    +data Update = Update {+                owner :: User,+                date :: String,+                updateType :: UpdateType+} deriving (Show)++instance FromJSON Update where+  parseJSON (Object v) = Update <$> v .: "owner"+                                <*> v .: "date"+                                <*> v .: "update_type"++data Timeframe = ThisWeek | LastWeek | TwoWeeks++instance Show Timeframe where+    show ThisWeek = "thisweek"+    show LastWeek = "lastweek"+    show TwoWeeks = "twoweeks"