rdioh-0.1.2: src/Rdioh.hs
{-# LANGUAGE FlexibleContexts #-}
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 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 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)
-- | 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
-- | The @Rdio@ monad...just a wrapper around a @ReaderT@ monad.
type Rdio a = ReaderT Token IO a
-- | used internally
mkExtras :: Show e => [e] -> (String, String)
mkExtras extras = ("extras", U.join "," $ show <$> extras)
-- | 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_
-- D.trace (B.unpack . rspPayload $ response) (return ())
let value = eitherDecode . rspPayload $ response
return $ rdioResult <$> value
-- RDIO methods
-- CORE methods TODO they all have multiple return types.
-- TODO currently unsupported because it has a variable specification.
-- All you can really do is request an Object back.
-- | 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)]
-- | 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)]
-- | 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)]
-- CATALOG methods
-- | Takes: a UPC code, [extras] (optional)
getAlbumsByUPC :: Int -> [AlbumExtra] -> Rdio (Either String [Album])
getAlbumsByUPC upc extras = runRequest $ [("method", "getAlbumsByUPC"), ("upc", toParam upc), mkExtras extras]
-- | Takes: A key of an artist
getAlbumsForArtist :: String -> Rdio (Either String [Album])
getAlbumsForArtist artist = getAlbumsForArtist' artist Nothing [] Nothing Nothing
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)
-- | Takes: a key of a label
getAlbumsForLabel :: String -> Rdio (Either String [Album])
getAlbumsForLabel label = getAlbumsForLabel' label [] Nothing Nothing
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)
-- | Takes: a key of a label
getArtistsForLabel :: String -> Rdio (Either String [Artist])
getArtistsForLabel label = getArtistsForLabel' label [] Nothing Nothing
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)
-- | Takes: an ISRC code, [extras] (optional)
getTracksByISRC :: String -> [TrackExtra] -> Rdio (Either String [Track])
getTracksByISRC isrc extras = runRequest $ [("method", "getTracksByISRC"), ("isrc", isrc), mkExtras extras]
-- | Takes: an artist key
getTracksForArtist :: String -> Rdio (Either String [Track])
getTracksForArtist artist = getTracksForArtist' artist Nothing [] Nothing Nothing
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)
-- 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
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)
return (results <$> res)
-- TODO searchSuggestions
-- | 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)]
-- | 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
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)
-- | Requires authentication OR use @getAlbumsInCollection'@ and pass in
-- a user key.
getAlbumsInCollection :: Rdio (Either String [Album])
getAlbumsInCollection = getAlbumsInCollection' Nothing Nothing Nothing Nothing Nothing []
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)
-- | Requires authentication OR use @getArtistsInCollection'@ and pass in
-- a user key.
getArtistsInCollection :: Rdio (Either String [Artist])
getArtistsInCollection = getArtistsInCollection' Nothing Nothing Nothing Nothing Nothing []
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)
-- | Requires authentication.
getOfflineTracks :: Rdio (Either String [Track])
getOfflineTracks = getOfflineTracks' Nothing Nothing []
getOfflineTracks' :: Maybe Int -> Maybe Int -> [TrackExtra] -> Rdio (Either String [Track])
getOfflineTracks' start count extras =
runRequest $ [("method", "getOfflineTracks"), mkExtras extras]
<+> ("start", start)
<+> ("count", count)
-- | 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 []
getTracksForAlbumInCollection' :: String -> Maybe String -> [TrackExtra] -> Rdio (Either String [Track])
getTracksForAlbumInCollection' album user extras =
runRequest $ [("method", "getTracksForAlbumInCollection"), ("album", album), mkExtras extras]
<+> ("user", user)
-- | 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 []
getTracksForArtistInCollection' :: String -> Maybe String -> [TrackExtra] -> Rdio (Either String [Track])
getTracksForArtistInCollection' artist user extras =
runRequest $ [("method", "getTracksForArtistInCollection"), ("artist", artist), mkExtras extras]
<+> ("user", user)
-- | Requires authentication OR use @getTracksInCollection'@ and pass in
-- a user key.
getTracksInCollection :: Rdio (Either String [Track])
getTracksInCollection = getTracksInCollection' Nothing Nothing Nothing Nothing Nothing []
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)
-- | Takes: a list of track or playlist keys. Requires authentication.
removeFromCollection :: [String] -> Rdio (Either String Bool)
removeFromCollection keys = runRequest $ [("method", "removeFromCollection"), ("keys", toParam keys)]
-- | 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)]
-- | 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]
-- | 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]
-- | Takes: a playlist key. Requires authentication.
deletePlaylist :: String -> Rdio (Either String Bool)
deletePlaylist playlist = runRequest $ [("method", "deletePlaylist"), ("playlist", playlist)]
-- | Requires authentication OR use @getPlaylists'@ and pass in a user key.
getPlaylists :: Rdio (Either String UserPlaylists)
getPlaylists = getPlaylists' Nothing [] Nothing
getPlaylists' :: Maybe String -> [PlaylistExtra] -> Maybe Bool -> Rdio (Either String UserPlaylists)
getPlaylists' user extras orderedList =
runRequest $ [("method", "getPlaylists"), mkExtras extras]
<+> ("user", user)
<+> ("ordered_list", orderedList)
-- | 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 []
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)
-- | 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]
-- | 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)]
-- | 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)]
-- | 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)]
-- | 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)