liblastfm 0.5.1 → 0.6.0
raw patch · 78 files changed
+2575/−2754 lines, 78 filesdep +cryptonitedep +transformersdep −crypto-apidep −pureMD5dep ~http-clientPVP ok
version bump matches the API change (PVP)
Dependencies added: cryptonite, transformers
Dependencies removed: crypto-api, pureMD5
Dependency ranges changed: http-client
API changes (from Hackage documentation)
- Network.Lastfm.Album: addTags :: Request f (Artist -> Album -> [Tag] -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Album: class ArtistAlbumOrMBID r a | a -> r
- Network.Lastfm.Album: getBuyLinks :: ArtistAlbumOrMBID Ready a => Request f (Country -> a)
- Network.Lastfm.Album: getInfo :: ArtistAlbumOrMBID Ready a => Request f a
- Network.Lastfm.Album: getShouts :: ArtistAlbumOrMBID Ready a => Request f a
- Network.Lastfm.Album: getTags :: ArtistAlbumOrMBID r a => Request f a
- Network.Lastfm.Album: getTopTags :: ArtistAlbumOrMBID Ready a => Request f a
- Network.Lastfm.Album: instance ArtistAlbumOrMBID r (Artist -> Album -> APIKey -> r)
- Network.Lastfm.Album: instance ArtistAlbumOrMBID r (MBID -> APIKey -> r)
- Network.Lastfm.Album: removeTag :: Request f (Artist -> Album -> Tag -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Album: search :: Request f (Album -> APIKey -> Ready)
- Network.Lastfm.Album: share :: Request f (Album -> Artist -> Recipient -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Artist: addTags :: Request f (Artist -> [Tag] -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Artist: class ArtistOrMBID r a
- Network.Lastfm.Artist: getCorrection :: Request f (Artist -> APIKey -> Ready)
- Network.Lastfm.Artist: getEvents :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
- Network.Lastfm.Artist: getInfo :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
- Network.Lastfm.Artist: getPastEvents :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
- Network.Lastfm.Artist: getPodcast :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
- Network.Lastfm.Artist: getShouts :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
- Network.Lastfm.Artist: getSimilar :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
- Network.Lastfm.Artist: getTags :: ArtistOrMBID r a => Request f (a -> APIKey -> r)
- Network.Lastfm.Artist: getTopAlbums :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
- Network.Lastfm.Artist: getTopFans :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
- Network.Lastfm.Artist: getTopTags :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
- Network.Lastfm.Artist: getTopTracks :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
- Network.Lastfm.Artist: instance ArtistOrMBID r Artist
- Network.Lastfm.Artist: instance ArtistOrMBID r MBID
- Network.Lastfm.Artist: removeTag :: Request f (Artist -> Tag -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Artist: search :: Request f (Artist -> APIKey -> Ready)
- Network.Lastfm.Artist: share :: Request f (Artist -> Recipient -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Artist: shout :: Request f (Artist -> Message -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Authentication: getMobileSession :: Request f (Username -> Password -> APIKey -> Sign)
- Network.Lastfm.Authentication: getSession :: Request f (Token -> APIKey -> Sign)
- Network.Lastfm.Authentication: getToken :: Request f (APIKey -> Ready)
- Network.Lastfm.Authentication: link :: Request f a -> String
- Network.Lastfm.Chart: getHypedArtists :: Request f (APIKey -> Ready)
- Network.Lastfm.Chart: getHypedTracks :: Request f (APIKey -> Ready)
- Network.Lastfm.Chart: getLovedTracks :: Request f (APIKey -> Ready)
- Network.Lastfm.Chart: getTopArtists :: Request f (APIKey -> Ready)
- Network.Lastfm.Chart: getTopTags :: Request f (APIKey -> Ready)
- Network.Lastfm.Chart: getTopTracks :: Request f (APIKey -> Ready)
- Network.Lastfm.Event: attend :: Request f (Event -> Status -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Event: getAttendees :: Request f (Event -> APIKey -> Ready)
- Network.Lastfm.Event: getInfo :: Request f (Event -> APIKey -> Ready)
- Network.Lastfm.Event: getShouts :: Request f (Event -> APIKey -> Ready)
- Network.Lastfm.Event: share :: Request f (Event -> Recipient -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Event: shout :: Request f (Event -> Message -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Geo: getEvents :: Request f (APIKey -> Ready)
- Network.Lastfm.Geo: getMetroArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)
- Network.Lastfm.Geo: getMetroHypeArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)
- Network.Lastfm.Geo: getMetroHypeTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)
- Network.Lastfm.Geo: getMetroTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)
- Network.Lastfm.Geo: getMetroUniqueArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)
- Network.Lastfm.Geo: getMetroUniqueTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)
- Network.Lastfm.Geo: getMetroWeeklyChartlist :: Request f (Metro -> APIKey -> Ready)
- Network.Lastfm.Geo: getMetros :: Request f (APIKey -> Ready)
- Network.Lastfm.Geo: getTopArtists :: Request f (Country -> APIKey -> Ready)
- Network.Lastfm.Geo: getTopTracks :: Request f (Country -> APIKey -> Ready)
- Network.Lastfm.Group: getHype :: Request f (Group -> APIKey -> Ready)
- Network.Lastfm.Group: getMembers :: Request f (Group -> APIKey -> Ready)
- Network.Lastfm.Group: getWeeklyAlbumChart :: Request f (Group -> APIKey -> Ready)
- Network.Lastfm.Group: getWeeklyArtistChart :: Request f (Group -> APIKey -> Ready)
- Network.Lastfm.Group: getWeeklyChartList :: Request f (Group -> APIKey -> Ready)
- Network.Lastfm.Group: getWeeklyTrackChart :: Request f (Group -> APIKey -> Ready)
- Network.Lastfm.Internal: JSON :: Format
- Network.Lastfm.Internal: R :: {-# UNPACK #-} !Text -> {-# UNPACK #-} !ByteString -> !(Map Text Text) -> R
- Network.Lastfm.Internal: Request :: Const (Dual (Endo (R f))) a -> Request f a
- Network.Lastfm.Internal: XML :: Format
- Network.Lastfm.Internal: _host :: R -> {-# UNPACK #-} !Text
- Network.Lastfm.Internal: _method :: R -> {-# UNPACK #-} !ByteString
- Network.Lastfm.Internal: _query :: R -> !(Map Text Text)
- Network.Lastfm.Internal: absorbQuery :: Foldable t => t (Request f b) -> Request f a
- Network.Lastfm.Internal: coerce :: Request f a -> Request f b
- Network.Lastfm.Internal: data Format
- Network.Lastfm.Internal: data R (f :: Format)
- Network.Lastfm.Internal: data Ready
- Network.Lastfm.Internal: data Sign
- Network.Lastfm.Internal: host :: Functor f => (Text -> f Text) -> R h -> f (R h)
- Network.Lastfm.Internal: indexedWith :: Int -> Request f a -> Request f a
- Network.Lastfm.Internal: instance Applicative (Request f)
- Network.Lastfm.Internal: instance Foldable (Request f)
- Network.Lastfm.Internal: instance Functor (Request f)
- Network.Lastfm.Internal: instance Serialize (R f)
- Network.Lastfm.Internal: instance Traversable (Request f)
- Network.Lastfm.Internal: instance Typeable 'JSON
- Network.Lastfm.Internal: instance Typeable 'XML
- Network.Lastfm.Internal: instance Typeable Format
- Network.Lastfm.Internal: instance Typeable R
- Network.Lastfm.Internal: instance Typeable Ready
- Network.Lastfm.Internal: instance Typeable Request
- Network.Lastfm.Internal: instance Typeable Sign
- Network.Lastfm.Internal: method :: Functor f => (ByteString -> f ByteString) -> R h -> f (R h)
- Network.Lastfm.Internal: newtype Request f a
- Network.Lastfm.Internal: query :: Functor f => (Map Text Text -> f (Map Text Text)) -> R h -> f (R h)
- Network.Lastfm.Internal: render :: R f -> String
- Network.Lastfm.Internal: unRequest :: Request f a -> Const (Dual (Endo (R f))) a
- Network.Lastfm.Internal: unwrap :: Request f a -> R f -> R f
- Network.Lastfm.Internal: wrap :: (R f -> R f) -> Request f a
- Network.Lastfm.Library: addAlbum :: NonEmpty (Request f LibraryAlbum) -> Request f (APIKey -> SessionKey -> Sign)
- Network.Lastfm.Library: addArtist :: NonEmpty (Request f LibraryArtist) -> Request f (APIKey -> SessionKey -> Sign)
- Network.Lastfm.Library: addTrack :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Library: albumItem :: Request f (Artist -> Album -> LibraryAlbum)
- Network.Lastfm.Library: artistItem :: Request f (Artist -> LibraryArtist)
- Network.Lastfm.Library: getAlbums :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.Library: getArtists :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.Library: getTracks :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.Library: removeAlbum :: Request f (Artist -> Album -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Library: removeArtist :: Request f (Artist -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Library: removeScrobble :: Request f (Artist -> Track -> Timestamp -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Library: removeTrack :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Playlist: addTrack :: Request f (Playlist -> Artist -> Track -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Playlist: create :: Request f (APIKey -> SessionKey -> Sign)
- Network.Lastfm.Radio: getPlaylist :: Request f (Multiplier -> Bitrate -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Radio: search :: Request f (Name -> APIKey -> Ready)
- Network.Lastfm.Radio: tune :: Request f (Station -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Request: Attending :: Status
- Network.Lastfm.Request: B128 :: Bitrate
- Network.Lastfm.Request: B64 :: Bitrate
- Network.Lastfm.Request: JSON :: Format
- Network.Lastfm.Request: M1 :: Multiplier
- Network.Lastfm.Request: M2 :: Multiplier
- Network.Lastfm.Request: Maybe :: Status
- Network.Lastfm.Request: NotAttending :: Status
- Network.Lastfm.Request: XML :: Format
- Network.Lastfm.Request: album :: Text -> Request f Album
- Network.Lastfm.Request: albumArtist :: Text -> Request f AlbumArtist
- Network.Lastfm.Request: api :: Text -> Request f a
- Network.Lastfm.Request: apiKey :: Text -> Request f APIKey
- Network.Lastfm.Request: artist :: Text -> Request f Artist
- Network.Lastfm.Request: artists :: [Text] -> Request f [Artist]
- Network.Lastfm.Request: autocorrect :: Bool -> Request f Autocorrect
- Network.Lastfm.Request: bitrate :: Bitrate -> Request f Bitrate
- Network.Lastfm.Request: buyLinks :: Bool -> Request f BuyLinks
- Network.Lastfm.Request: callback :: Text -> Request f Callback
- Network.Lastfm.Request: chosenByUser :: Bool -> Request f ChosenByUser
- Network.Lastfm.Request: class Targeted a
- Network.Lastfm.Request: comparison :: Targeted a => Int64 -> Request f a -> Request f a
- Network.Lastfm.Request: context :: Text -> Request f Context
- Network.Lastfm.Request: country :: Text -> Request f Country
- Network.Lastfm.Request: data APIKey
- Network.Lastfm.Request: data Album
- Network.Lastfm.Request: data AlbumArtist
- Network.Lastfm.Request: data Artist
- Network.Lastfm.Request: data Autocorrect
- Network.Lastfm.Request: data Bitrate
- Network.Lastfm.Request: data BuyLinks
- Network.Lastfm.Request: data Callback
- Network.Lastfm.Request: data ChosenByUser
- Network.Lastfm.Request: data Context
- Network.Lastfm.Request: data Country
- Network.Lastfm.Request: data Description
- Network.Lastfm.Request: data Discovery
- Network.Lastfm.Request: data Distance
- Network.Lastfm.Request: data Duration
- Network.Lastfm.Request: data End
- Network.Lastfm.Request: data EndTimestamp
- Network.Lastfm.Request: data Event
- Network.Lastfm.Request: data Festivals
- Network.Lastfm.Request: data Fingerprint
- Network.Lastfm.Request: data Format
- Network.Lastfm.Request: data From
- Network.Lastfm.Request: data Group
- Network.Lastfm.Request: data Language
- Network.Lastfm.Request: data Latitude
- Network.Lastfm.Request: data LibraryAlbum
- Network.Lastfm.Request: data LibraryArtist
- Network.Lastfm.Request: data Limit
- Network.Lastfm.Request: data Location
- Network.Lastfm.Request: data Longitude
- Network.Lastfm.Request: data MBID
- Network.Lastfm.Request: data Message
- Network.Lastfm.Request: data Metro
- Network.Lastfm.Request: data Multiplier
- Network.Lastfm.Request: data Name
- Network.Lastfm.Request: data Page
- Network.Lastfm.Request: data Password
- Network.Lastfm.Request: data Playlist
- Network.Lastfm.Request: data Public
- Network.Lastfm.Request: data R (f :: Format)
- Network.Lastfm.Request: data RTP
- Network.Lastfm.Request: data Ready
- Network.Lastfm.Request: data RecentTracks
- Network.Lastfm.Request: data Recipient
- Network.Lastfm.Request: data Request f a
- Network.Lastfm.Request: data Scrobble
- Network.Lastfm.Request: data SessionKey
- Network.Lastfm.Request: data Sign
- Network.Lastfm.Request: data Start
- Network.Lastfm.Request: data StartTimestamp
- Network.Lastfm.Request: data Station
- Network.Lastfm.Request: data Status
- Network.Lastfm.Request: data StreamId
- Network.Lastfm.Request: data Tag
- Network.Lastfm.Request: data TaggingType
- Network.Lastfm.Request: data Timestamp
- Network.Lastfm.Request: data Title
- Network.Lastfm.Request: data To
- Network.Lastfm.Request: data Token
- Network.Lastfm.Request: data Track
- Network.Lastfm.Request: data TrackNumber
- Network.Lastfm.Request: data UseRecs
- Network.Lastfm.Request: data User
- Network.Lastfm.Request: data Username
- Network.Lastfm.Request: data Venue
- Network.Lastfm.Request: data VenueName
- Network.Lastfm.Request: description :: Text -> Request f Description
- Network.Lastfm.Request: discovery :: Bool -> Request f Discovery
- Network.Lastfm.Request: distance :: Int64 -> Request f Distance
- Network.Lastfm.Request: duration :: Int64 -> Request f Duration
- Network.Lastfm.Request: end :: Int64 -> Request f End
- Network.Lastfm.Request: endTimestamp :: Int64 -> Request f EndTimestamp
- Network.Lastfm.Request: event :: Int64 -> Request f Event
- Network.Lastfm.Request: festivalsonly :: Bool -> Request f Festivals
- Network.Lastfm.Request: fingerprint :: Int64 -> Request f Fingerprint
- Network.Lastfm.Request: from :: Int64 -> Request f From
- Network.Lastfm.Request: get :: Request f a
- Network.Lastfm.Request: group :: Text -> Request f Group
- Network.Lastfm.Request: instance Argument Bool
- Network.Lastfm.Request: instance Argument Int64
- Network.Lastfm.Request: instance Argument Text
- Network.Lastfm.Request: instance Argument a => Argument [a]
- Network.Lastfm.Request: instance Targeted User
- Network.Lastfm.Request: instance Targeted [Artist]
- Network.Lastfm.Request: json :: Request JSON a
- Network.Lastfm.Request: language :: Text -> Request f Language
- Network.Lastfm.Request: latitude :: Text -> Request f Latitude
- Network.Lastfm.Request: limit :: Int64 -> Request f Limit
- Network.Lastfm.Request: location :: Text -> Request f Location
- Network.Lastfm.Request: longitude :: Text -> Request f Longitude
- Network.Lastfm.Request: mbid :: Text -> Request f MBID
- Network.Lastfm.Request: message :: Text -> Request f Message
- Network.Lastfm.Request: metro :: Text -> Request f Metro
- Network.Lastfm.Request: multiplier :: Multiplier -> Request f Multiplier
- Network.Lastfm.Request: name :: Text -> Request f Name
- Network.Lastfm.Request: page :: Int64 -> Request f Page
- Network.Lastfm.Request: password :: Text -> Request f Password
- Network.Lastfm.Request: playlist :: Int64 -> Request f Playlist
- Network.Lastfm.Request: post :: Request f a
- Network.Lastfm.Request: public :: Bool -> Request f Public
- Network.Lastfm.Request: recentTracks :: Bool -> Request f RecentTracks
- Network.Lastfm.Request: recipient :: Text -> Request f Recipient
- Network.Lastfm.Request: rtp :: Bool -> Request f RTP
- Network.Lastfm.Request: sessionKey :: Text -> Request f SessionKey
- Network.Lastfm.Request: start :: Int64 -> Request f Start
- Network.Lastfm.Request: startTimestamp :: Int64 -> Request f StartTimestamp
- Network.Lastfm.Request: station :: Text -> Request f Station
- Network.Lastfm.Request: status :: Status -> Request f Status
- Network.Lastfm.Request: streamId :: Int64 -> Request f StreamId
- Network.Lastfm.Request: tag :: Text -> Request f Tag
- Network.Lastfm.Request: taggingType :: Text -> Request f TaggingType
- Network.Lastfm.Request: tags :: [Text] -> Request f [Tag]
- Network.Lastfm.Request: timestamp :: Int64 -> Request f Timestamp
- Network.Lastfm.Request: title :: Text -> Request f Title
- Network.Lastfm.Request: to :: Int64 -> Request f To
- Network.Lastfm.Request: token :: Text -> Request f Token
- Network.Lastfm.Request: track :: Text -> Request f Track
- Network.Lastfm.Request: trackNumber :: Int64 -> Request f TrackNumber
- Network.Lastfm.Request: useRecs :: Bool -> Request f UseRecs
- Network.Lastfm.Request: user :: Text -> Request f User
- Network.Lastfm.Request: username :: Text -> Request f Username
- Network.Lastfm.Request: venue :: Int64 -> Request f Venue
- Network.Lastfm.Request: venueName :: Text -> Request f VenueName
- Network.Lastfm.Request: xml :: Request XML a
- Network.Lastfm.Response: JSON :: Format
- Network.Lastfm.Response: LastfmBadResponse :: ByteString -> LastfmError
- Network.Lastfm.Response: LastfmEncodedError :: Int -> Text -> LastfmError
- Network.Lastfm.Response: LastfmHttpError :: HttpException -> LastfmError
- Network.Lastfm.Response: Secret :: Text -> Secret
- Network.Lastfm.Response: XML :: Format
- Network.Lastfm.Response: _LastfmBadResponse :: (Choice p, Applicative m, AsLastfmError e) => p ByteString (m ByteString) -> p e (m e)
- Network.Lastfm.Response: _LastfmEncodedError :: (Choice p, Applicative m, AsLastfmError e) => p (Int, Text) (m (Int, Text)) -> p e (m e)
- Network.Lastfm.Response: _LastfmHttpError :: (Choice p, Applicative m, AsLastfmError e) => p HttpException (m HttpException) -> p e (m e)
- Network.Lastfm.Response: class Supported f r | f -> r, r -> f
- Network.Lastfm.Response: closeConnection :: Connection -> IO ()
- Network.Lastfm.Response: data Connection
- Network.Lastfm.Response: data Format
- Network.Lastfm.Response: data LastfmError
- Network.Lastfm.Response: instance AsLastfmError LastfmError
- Network.Lastfm.Response: instance AsLastfmError SomeException
- Network.Lastfm.Response: instance Eq LastfmError
- Network.Lastfm.Response: instance Eq Secret
- Network.Lastfm.Response: instance Exception LastfmError
- Network.Lastfm.Response: instance IsString Secret
- Network.Lastfm.Response: instance Show LastfmError
- Network.Lastfm.Response: instance Show Secret
- Network.Lastfm.Response: instance Supported 'JSON Value
- Network.Lastfm.Response: instance Supported 'XML Document
- Network.Lastfm.Response: instance Typeable LastfmError
- Network.Lastfm.Response: instance Typeable Secret
- Network.Lastfm.Response: lastfm :: Supported f r => Connection -> Request f Ready -> IO (Either LastfmError r)
- Network.Lastfm.Response: lastfm_ :: Supported f r => Connection -> Request f Ready -> IO (Either LastfmError ())
- Network.Lastfm.Response: newConnection :: IO Connection
- Network.Lastfm.Response: newtype Secret
- Network.Lastfm.Response: sign :: Secret -> Request f Sign -> Request f Ready
- Network.Lastfm.Response: withConnection :: (Connection -> IO a) -> IO a
- Network.Lastfm.Tag: getInfo :: Request f (Tag -> APIKey -> Ready)
- Network.Lastfm.Tag: getSimilar :: Request f (Tag -> APIKey -> Ready)
- Network.Lastfm.Tag: getTopAlbums :: Request f (Tag -> APIKey -> Ready)
- Network.Lastfm.Tag: getTopArtists :: Request f (Tag -> APIKey -> Ready)
- Network.Lastfm.Tag: getTopTags :: Request f (APIKey -> Ready)
- Network.Lastfm.Tag: getTopTracks :: Request f (Tag -> APIKey -> Ready)
- Network.Lastfm.Tag: getWeeklyArtistChart :: Request f (Tag -> APIKey -> Ready)
- Network.Lastfm.Tag: getWeeklyChartList :: Request f (Tag -> APIKey -> Ready)
- Network.Lastfm.Tag: search :: Request f (Tag -> APIKey -> Ready)
- Network.Lastfm.Tasteometer: compare :: (Targeted u, Targeted v) => Request f u -> Request f v -> Request f (APIKey -> Ready)
- Network.Lastfm.Track: addTags :: Request f (Artist -> Track -> [Tag] -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Track: ban :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Track: class ArtistTrackOrMBID a
- Network.Lastfm.Track: getBuyLinks :: ArtistTrackOrMBID t => Request f (Country -> t)
- Network.Lastfm.Track: getCorrection :: Request f (Artist -> Track -> APIKey -> Ready)
- Network.Lastfm.Track: getFingerprintMetadata :: Request f (Fingerprint -> APIKey -> Ready)
- Network.Lastfm.Track: getInfo :: ArtistTrackOrMBID t => Request f t
- Network.Lastfm.Track: getShouts :: ArtistTrackOrMBID t => Request f t
- Network.Lastfm.Track: getSimilar :: ArtistTrackOrMBID t => Request f t
- Network.Lastfm.Track: getTags :: ArtistTrackOrMBID t => Request f t
- Network.Lastfm.Track: getTopFans :: ArtistTrackOrMBID t => Request f t
- Network.Lastfm.Track: getTopTags :: ArtistTrackOrMBID t => Request f t
- Network.Lastfm.Track: instance ArtistTrackOrMBID (Artist -> Track -> APIKey -> Ready)
- Network.Lastfm.Track: instance ArtistTrackOrMBID (MBID -> APIKey -> Ready)
- Network.Lastfm.Track: item :: Request f (Artist -> Track -> Timestamp -> Scrobble)
- Network.Lastfm.Track: love :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Track: removeTag :: Request f (Artist -> Track -> Tag -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Track: scrobble :: NonEmpty (Request f Scrobble) -> Request f (APIKey -> SessionKey -> Sign)
- Network.Lastfm.Track: search :: Request f (Track -> APIKey -> Ready)
- Network.Lastfm.Track: share :: Request f (Artist -> Track -> Recipient -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Track: unban :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Track: unlove :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Track: updateNowPlaying :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.User: getArtistTracks :: Request f (User -> Artist -> APIKey -> Ready)
- Network.Lastfm.User: getBannedTracks :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getEvents :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getFriends :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getInfo :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getLovedTracks :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getNeighbours :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getNewReleases :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getPastEvents :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getPersonalTags :: Request f (User -> Tag -> TaggingType -> APIKey -> Ready)
- Network.Lastfm.User: getPlaylists :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getRecentStations :: Request f (User -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.User: getRecentTracks :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getRecommendedArtists :: Request f (APIKey -> SessionKey -> Sign)
- Network.Lastfm.User: getRecommendedEvents :: Request f (APIKey -> SessionKey -> Sign)
- Network.Lastfm.User: getShouts :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getTopAlbums :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getTopArtists :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getTopTags :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getTopTracks :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getWeeklyAlbumChart :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getWeeklyArtistChart :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getWeeklyChartList :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: getWeeklyTrackChart :: Request f (User -> APIKey -> Ready)
- Network.Lastfm.User: shout :: Request f (User -> Message -> APIKey -> SessionKey -> Sign)
- Network.Lastfm.Venue: getEvents :: Request f (Venue -> APIKey -> Ready)
- Network.Lastfm.Venue: getPastEvents :: Request f (Venue -> APIKey -> Ready)
- Network.Lastfm.Venue: search :: Request f (VenueName -> APIKey -> Ready)
+ Lastfm.Album: addTags :: Request f (Artist -> Album -> [Tag] -> APIKey -> SessionKey -> Sign)
+ Lastfm.Album: class ArtistAlbumOrMBID r a | a -> r
+ Lastfm.Album: getBuyLinks :: ArtistAlbumOrMBID Ready a => Request f (Country -> a)
+ Lastfm.Album: getInfo :: ArtistAlbumOrMBID Ready a => Request f a
+ Lastfm.Album: getShouts :: ArtistAlbumOrMBID Ready a => Request f a
+ Lastfm.Album: getTags :: ArtistAlbumOrMBID r a => Request f a
+ Lastfm.Album: getTopTags :: ArtistAlbumOrMBID Ready a => Request f a
+ Lastfm.Album: instance Lastfm.Album.ArtistAlbumOrMBID r (Lastfm.Request.Artist -> Lastfm.Request.Album -> Lastfm.Request.APIKey -> r)
+ Lastfm.Album: instance Lastfm.Album.ArtistAlbumOrMBID r (Lastfm.Request.MBID -> Lastfm.Request.APIKey -> r)
+ Lastfm.Album: removeTag :: Request f (Artist -> Album -> Tag -> APIKey -> SessionKey -> Sign)
+ Lastfm.Album: search :: Request f (Album -> APIKey -> Ready)
+ Lastfm.Album: share :: Request f (Album -> Artist -> Recipient -> APIKey -> SessionKey -> Sign)
+ Lastfm.Artist: addTags :: Request f (Artist -> [Tag] -> APIKey -> SessionKey -> Sign)
+ Lastfm.Artist: class ArtistOrMBID r a
+ Lastfm.Artist: getCorrection :: Request f (Artist -> APIKey -> Ready)
+ Lastfm.Artist: getEvents :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
+ Lastfm.Artist: getInfo :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
+ Lastfm.Artist: getPastEvents :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
+ Lastfm.Artist: getPodcast :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
+ Lastfm.Artist: getShouts :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
+ Lastfm.Artist: getSimilar :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
+ Lastfm.Artist: getTags :: ArtistOrMBID r a => Request f (a -> APIKey -> r)
+ Lastfm.Artist: getTopAlbums :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
+ Lastfm.Artist: getTopFans :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
+ Lastfm.Artist: getTopTags :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
+ Lastfm.Artist: getTopTracks :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)
+ Lastfm.Artist: instance Lastfm.Artist.ArtistOrMBID r Lastfm.Request.Artist
+ Lastfm.Artist: instance Lastfm.Artist.ArtistOrMBID r Lastfm.Request.MBID
+ Lastfm.Artist: removeTag :: Request f (Artist -> Tag -> APIKey -> SessionKey -> Sign)
+ Lastfm.Artist: search :: Request f (Artist -> APIKey -> Ready)
+ Lastfm.Artist: share :: Request f (Artist -> Recipient -> APIKey -> SessionKey -> Sign)
+ Lastfm.Artist: shout :: Request f (Artist -> Message -> APIKey -> SessionKey -> Sign)
+ Lastfm.Authentication: getMobileSession :: Request f (Username -> Password -> APIKey -> Sign)
+ Lastfm.Authentication: getSession :: Request f (Token -> APIKey -> Sign)
+ Lastfm.Authentication: getToken :: Request f (APIKey -> Ready)
+ Lastfm.Authentication: link :: Request f a -> String
+ Lastfm.Chart: getHypedArtists :: Request f (APIKey -> Ready)
+ Lastfm.Chart: getHypedTracks :: Request f (APIKey -> Ready)
+ Lastfm.Chart: getLovedTracks :: Request f (APIKey -> Ready)
+ Lastfm.Chart: getTopArtists :: Request f (APIKey -> Ready)
+ Lastfm.Chart: getTopTags :: Request f (APIKey -> Ready)
+ Lastfm.Chart: getTopTracks :: Request f (APIKey -> Ready)
+ Lastfm.Event: attend :: Request f (Event -> Status -> APIKey -> SessionKey -> Sign)
+ Lastfm.Event: getAttendees :: Request f (Event -> APIKey -> Ready)
+ Lastfm.Event: getInfo :: Request f (Event -> APIKey -> Ready)
+ Lastfm.Event: getShouts :: Request f (Event -> APIKey -> Ready)
+ Lastfm.Event: share :: Request f (Event -> Recipient -> APIKey -> SessionKey -> Sign)
+ Lastfm.Event: shout :: Request f (Event -> Message -> APIKey -> SessionKey -> Sign)
+ Lastfm.Geo: getEvents :: Request f (APIKey -> Ready)
+ Lastfm.Geo: getMetroArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)
+ Lastfm.Geo: getMetroHypeArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)
+ Lastfm.Geo: getMetroHypeTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)
+ Lastfm.Geo: getMetroTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)
+ Lastfm.Geo: getMetroUniqueArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)
+ Lastfm.Geo: getMetroUniqueTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)
+ Lastfm.Geo: getMetroWeeklyChartlist :: Request f (Metro -> APIKey -> Ready)
+ Lastfm.Geo: getMetros :: Request f (APIKey -> Ready)
+ Lastfm.Geo: getTopArtists :: Request f (Country -> APIKey -> Ready)
+ Lastfm.Geo: getTopTracks :: Request f (Country -> APIKey -> Ready)
+ Lastfm.Group: getHype :: Request f (Group -> APIKey -> Ready)
+ Lastfm.Group: getMembers :: Request f (Group -> APIKey -> Ready)
+ Lastfm.Group: getWeeklyAlbumChart :: Request f (Group -> APIKey -> Ready)
+ Lastfm.Group: getWeeklyArtistChart :: Request f (Group -> APIKey -> Ready)
+ Lastfm.Group: getWeeklyChartList :: Request f (Group -> APIKey -> Ready)
+ Lastfm.Group: getWeeklyTrackChart :: Request f (Group -> APIKey -> Ready)
+ Lastfm.Internal: JSON :: Format
+ Lastfm.Internal: R :: {-# UNPACK #-} !Text -> {-# UNPACK #-} !ByteString -> !(Map Text Text) -> R
+ Lastfm.Internal: Request :: Const (Dual (Endo (R f))) a -> Request f a
+ Lastfm.Internal: XML :: Format
+ Lastfm.Internal: [_host] :: R -> {-# UNPACK #-} !Text
+ Lastfm.Internal: [_method] :: R -> {-# UNPACK #-} !ByteString
+ Lastfm.Internal: [_query] :: R -> !(Map Text Text)
+ Lastfm.Internal: [unRequest] :: Request f a -> Const (Dual (Endo (R f))) a
+ Lastfm.Internal: absorbQuery :: Foldable t => t (Request f b) -> Request f a
+ Lastfm.Internal: coerce :: Request f a -> Request f b
+ Lastfm.Internal: data Format
+ Lastfm.Internal: data R (f :: Format)
+ Lastfm.Internal: data Ready
+ Lastfm.Internal: data Sign
+ Lastfm.Internal: indexedWith :: Int -> Request f a -> Request f a
+ Lastfm.Internal: instance Data.Foldable.Foldable (Lastfm.Internal.Request f)
+ Lastfm.Internal: instance Data.Serialize.Serialize (Lastfm.Internal.R f)
+ Lastfm.Internal: instance Data.Traversable.Traversable (Lastfm.Internal.Request f)
+ Lastfm.Internal: instance GHC.Base.Applicative (Lastfm.Internal.Request f)
+ Lastfm.Internal: instance GHC.Base.Functor (Lastfm.Internal.Request f)
+ Lastfm.Internal: newtype Request f a
+ Lastfm.Internal: render :: R f -> String
+ Lastfm.Internal: unwrap :: Request f a -> R f -> R f
+ Lastfm.Internal: wrap :: (R f -> R f) -> Request f a
+ Lastfm.Library: addAlbum :: NonEmpty (Request f LibraryAlbum) -> Request f (APIKey -> SessionKey -> Sign)
+ Lastfm.Library: addArtist :: NonEmpty (Request f LibraryArtist) -> Request f (APIKey -> SessionKey -> Sign)
+ Lastfm.Library: addTrack :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
+ Lastfm.Library: albumItem :: Request f (Artist -> Album -> LibraryAlbum)
+ Lastfm.Library: artistItem :: Request f (Artist -> LibraryArtist)
+ Lastfm.Library: getAlbums :: Request f (User -> APIKey -> Ready)
+ Lastfm.Library: getArtists :: Request f (User -> APIKey -> Ready)
+ Lastfm.Library: getTracks :: Request f (User -> APIKey -> Ready)
+ Lastfm.Library: removeAlbum :: Request f (Artist -> Album -> APIKey -> SessionKey -> Sign)
+ Lastfm.Library: removeArtist :: Request f (Artist -> APIKey -> SessionKey -> Sign)
+ Lastfm.Library: removeScrobble :: Request f (Artist -> Track -> Timestamp -> APIKey -> SessionKey -> Sign)
+ Lastfm.Library: removeTrack :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
+ Lastfm.Playlist: addTrack :: Request f (Playlist -> Artist -> Track -> APIKey -> SessionKey -> Sign)
+ Lastfm.Playlist: create :: Request f (APIKey -> SessionKey -> Sign)
+ Lastfm.Radio: getPlaylist :: Request f (Multiplier -> Bitrate -> APIKey -> SessionKey -> Sign)
+ Lastfm.Radio: search :: Request f (Name -> APIKey -> Ready)
+ Lastfm.Radio: tune :: Request f (Station -> APIKey -> SessionKey -> Sign)
+ Lastfm.Request: Attending :: Status
+ Lastfm.Request: B128 :: Bitrate
+ Lastfm.Request: B64 :: Bitrate
+ Lastfm.Request: JSON :: Format
+ Lastfm.Request: M1 :: Multiplier
+ Lastfm.Request: M2 :: Multiplier
+ Lastfm.Request: Maybe :: Status
+ Lastfm.Request: NotAttending :: Status
+ Lastfm.Request: XML :: Format
+ Lastfm.Request: album :: Text -> Request f Album
+ Lastfm.Request: albumArtist :: Text -> Request f AlbumArtist
+ Lastfm.Request: api :: Text -> Request f a
+ Lastfm.Request: apiKey :: Text -> Request f APIKey
+ Lastfm.Request: artist :: Text -> Request f Artist
+ Lastfm.Request: artists :: [Text] -> Request f [Artist]
+ Lastfm.Request: autocorrect :: Bool -> Request f Autocorrect
+ Lastfm.Request: bitrate :: Bitrate -> Request f Bitrate
+ Lastfm.Request: buyLinks :: Bool -> Request f BuyLinks
+ Lastfm.Request: callback :: Text -> Request f Callback
+ Lastfm.Request: chosenByUser :: Bool -> Request f ChosenByUser
+ Lastfm.Request: class Targeted a
+ Lastfm.Request: comparison :: Targeted a => Int64 -> Request f a -> Request f a
+ Lastfm.Request: context :: Text -> Request f Context
+ Lastfm.Request: country :: Text -> Request f Country
+ Lastfm.Request: data APIKey
+ Lastfm.Request: data Album
+ Lastfm.Request: data AlbumArtist
+ Lastfm.Request: data Artist
+ Lastfm.Request: data Autocorrect
+ Lastfm.Request: data Bitrate
+ Lastfm.Request: data BuyLinks
+ Lastfm.Request: data Callback
+ Lastfm.Request: data ChosenByUser
+ Lastfm.Request: data Context
+ Lastfm.Request: data Country
+ Lastfm.Request: data Description
+ Lastfm.Request: data Discovery
+ Lastfm.Request: data Distance
+ Lastfm.Request: data Duration
+ Lastfm.Request: data End
+ Lastfm.Request: data EndTimestamp
+ Lastfm.Request: data Event
+ Lastfm.Request: data Festivals
+ Lastfm.Request: data Fingerprint
+ Lastfm.Request: data Format
+ Lastfm.Request: data From
+ Lastfm.Request: data Group
+ Lastfm.Request: data Language
+ Lastfm.Request: data Latitude
+ Lastfm.Request: data LibraryAlbum
+ Lastfm.Request: data LibraryArtist
+ Lastfm.Request: data Limit
+ Lastfm.Request: data Location
+ Lastfm.Request: data Longitude
+ Lastfm.Request: data MBID
+ Lastfm.Request: data Message
+ Lastfm.Request: data Metro
+ Lastfm.Request: data Multiplier
+ Lastfm.Request: data Name
+ Lastfm.Request: data Page
+ Lastfm.Request: data Password
+ Lastfm.Request: data Playlist
+ Lastfm.Request: data Public
+ Lastfm.Request: data R (f :: Format)
+ Lastfm.Request: data RTP
+ Lastfm.Request: data Ready
+ Lastfm.Request: data RecentTracks
+ Lastfm.Request: data Recipient
+ Lastfm.Request: data Request f a
+ Lastfm.Request: data Scrobble
+ Lastfm.Request: data SessionKey
+ Lastfm.Request: data Sign
+ Lastfm.Request: data Start
+ Lastfm.Request: data StartTimestamp
+ Lastfm.Request: data Station
+ Lastfm.Request: data Status
+ Lastfm.Request: data StreamId
+ Lastfm.Request: data Tag
+ Lastfm.Request: data TaggingType
+ Lastfm.Request: data Timestamp
+ Lastfm.Request: data Title
+ Lastfm.Request: data To
+ Lastfm.Request: data Token
+ Lastfm.Request: data Track
+ Lastfm.Request: data TrackNumber
+ Lastfm.Request: data UseRecs
+ Lastfm.Request: data User
+ Lastfm.Request: data Username
+ Lastfm.Request: data Venue
+ Lastfm.Request: data VenueName
+ Lastfm.Request: description :: Text -> Request f Description
+ Lastfm.Request: discovery :: Bool -> Request f Discovery
+ Lastfm.Request: distance :: Int64 -> Request f Distance
+ Lastfm.Request: duration :: Int64 -> Request f Duration
+ Lastfm.Request: end :: Int64 -> Request f End
+ Lastfm.Request: endTimestamp :: Int64 -> Request f EndTimestamp
+ Lastfm.Request: event :: Int64 -> Request f Event
+ Lastfm.Request: festivalsonly :: Bool -> Request f Festivals
+ Lastfm.Request: fingerprint :: Int64 -> Request f Fingerprint
+ Lastfm.Request: from :: Int64 -> Request f From
+ Lastfm.Request: get :: Request f a
+ Lastfm.Request: group :: Text -> Request f Group
+ Lastfm.Request: https :: Request f a
+ Lastfm.Request: instance Lastfm.Request.Argument Data.Text.Internal.Text
+ Lastfm.Request: instance Lastfm.Request.Argument GHC.Int.Int64
+ Lastfm.Request: instance Lastfm.Request.Argument GHC.Types.Bool
+ Lastfm.Request: instance Lastfm.Request.Argument a => Lastfm.Request.Argument [a]
+ Lastfm.Request: instance Lastfm.Request.Targeted Lastfm.Request.User
+ Lastfm.Request: instance Lastfm.Request.Targeted [Lastfm.Request.Artist]
+ Lastfm.Request: json :: Request JSON a
+ Lastfm.Request: language :: Text -> Request f Language
+ Lastfm.Request: latitude :: Text -> Request f Latitude
+ Lastfm.Request: limit :: Int64 -> Request f Limit
+ Lastfm.Request: location :: Text -> Request f Location
+ Lastfm.Request: longitude :: Text -> Request f Longitude
+ Lastfm.Request: mbid :: Text -> Request f MBID
+ Lastfm.Request: message :: Text -> Request f Message
+ Lastfm.Request: metro :: Text -> Request f Metro
+ Lastfm.Request: multiplier :: Multiplier -> Request f Multiplier
+ Lastfm.Request: name :: Text -> Request f Name
+ Lastfm.Request: page :: Int64 -> Request f Page
+ Lastfm.Request: password :: Text -> Request f Password
+ Lastfm.Request: playlist :: Int64 -> Request f Playlist
+ Lastfm.Request: post :: Request f a
+ Lastfm.Request: public :: Bool -> Request f Public
+ Lastfm.Request: recentTracks :: Bool -> Request f RecentTracks
+ Lastfm.Request: recipient :: Text -> Request f Recipient
+ Lastfm.Request: rtp :: Bool -> Request f RTP
+ Lastfm.Request: sessionKey :: Text -> Request f SessionKey
+ Lastfm.Request: start :: Int64 -> Request f Start
+ Lastfm.Request: startTimestamp :: Int64 -> Request f StartTimestamp
+ Lastfm.Request: station :: Text -> Request f Station
+ Lastfm.Request: status :: Status -> Request f Status
+ Lastfm.Request: streamId :: Int64 -> Request f StreamId
+ Lastfm.Request: tag :: Text -> Request f Tag
+ Lastfm.Request: taggingType :: Text -> Request f TaggingType
+ Lastfm.Request: tags :: [Text] -> Request f [Tag]
+ Lastfm.Request: timestamp :: Int64 -> Request f Timestamp
+ Lastfm.Request: title :: Text -> Request f Title
+ Lastfm.Request: to :: Int64 -> Request f To
+ Lastfm.Request: token :: Text -> Request f Token
+ Lastfm.Request: track :: Text -> Request f Track
+ Lastfm.Request: trackNumber :: Int64 -> Request f TrackNumber
+ Lastfm.Request: useRecs :: Bool -> Request f UseRecs
+ Lastfm.Request: user :: Text -> Request f User
+ Lastfm.Request: username :: Text -> Request f Username
+ Lastfm.Request: venue :: Int64 -> Request f Venue
+ Lastfm.Request: venueName :: Text -> Request f VenueName
+ Lastfm.Request: xml :: Request XML a
+ Lastfm.Response: JSON :: Format
+ Lastfm.Response: LastfmBadResponse :: ByteString -> LastfmError
+ Lastfm.Response: LastfmEncodedError :: Int -> Text -> LastfmError
+ Lastfm.Response: LastfmHttpError :: HttpException -> LastfmError
+ Lastfm.Response: Secret :: Text -> Secret
+ Lastfm.Response: XML :: Format
+ Lastfm.Response: _LastfmBadResponse :: (Choice p, Applicative m, AsLastfmError e) => p ByteString (m ByteString) -> p e (m e)
+ Lastfm.Response: _LastfmEncodedError :: (Choice p, Applicative m, AsLastfmError e) => p (Int, Text) (m (Int, Text)) -> p e (m e)
+ Lastfm.Response: _LastfmHttpError :: (Choice p, Applicative m, AsLastfmError e) => p HttpException (m HttpException) -> p e (m e)
+ Lastfm.Response: class Supported f r | f -> r, r -> f
+ Lastfm.Response: data Connection
+ Lastfm.Response: data Format
+ Lastfm.Response: data LastfmError
+ Lastfm.Response: instance Data.String.IsString Lastfm.Response.Secret
+ Lastfm.Response: instance GHC.Classes.Eq Lastfm.Response.LastfmError
+ Lastfm.Response: instance GHC.Classes.Eq Lastfm.Response.Secret
+ Lastfm.Response: instance GHC.Exception.Exception Lastfm.Response.LastfmError
+ Lastfm.Response: instance GHC.Show.Show Lastfm.Response.LastfmError
+ Lastfm.Response: instance GHC.Show.Show Lastfm.Response.Secret
+ Lastfm.Response: instance Lastfm.Response.AsLastfmError GHC.Exception.SomeException
+ Lastfm.Response: instance Lastfm.Response.AsLastfmError Lastfm.Response.LastfmError
+ Lastfm.Response: instance Lastfm.Response.Supported 'Lastfm.Internal.JSON Data.Aeson.Types.Internal.Value
+ Lastfm.Response: instance Lastfm.Response.Supported 'Lastfm.Internal.XML Text.XML.Document
+ Lastfm.Response: lastfm :: Supported f r => Connection -> Request f Ready -> IO (Either LastfmError r)
+ Lastfm.Response: lastfm_ :: Supported f r => Connection -> Request f Ready -> IO (Either LastfmError ())
+ Lastfm.Response: newConnection :: MonadIO m => m Connection
+ Lastfm.Response: newtype Secret
+ Lastfm.Response: sign :: Secret -> Request f Sign -> Request f Ready
+ Lastfm.Response: withConnection :: MonadIO m => (Connection -> m a) -> m a
+ Lastfm.Tag: getInfo :: Request f (Tag -> APIKey -> Ready)
+ Lastfm.Tag: getSimilar :: Request f (Tag -> APIKey -> Ready)
+ Lastfm.Tag: getTopAlbums :: Request f (Tag -> APIKey -> Ready)
+ Lastfm.Tag: getTopArtists :: Request f (Tag -> APIKey -> Ready)
+ Lastfm.Tag: getTopTags :: Request f (APIKey -> Ready)
+ Lastfm.Tag: getTopTracks :: Request f (Tag -> APIKey -> Ready)
+ Lastfm.Tag: getWeeklyArtistChart :: Request f (Tag -> APIKey -> Ready)
+ Lastfm.Tag: getWeeklyChartList :: Request f (Tag -> APIKey -> Ready)
+ Lastfm.Tag: search :: Request f (Tag -> APIKey -> Ready)
+ Lastfm.Tasteometer: compare :: (Targeted u, Targeted v) => Request f u -> Request f v -> Request f (APIKey -> Ready)
+ Lastfm.Track: addTags :: Request f (Artist -> Track -> [Tag] -> APIKey -> SessionKey -> Sign)
+ Lastfm.Track: ban :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
+ Lastfm.Track: class ArtistTrackOrMBID a
+ Lastfm.Track: getBuyLinks :: ArtistTrackOrMBID t => Request f (Country -> t)
+ Lastfm.Track: getCorrection :: Request f (Artist -> Track -> APIKey -> Ready)
+ Lastfm.Track: getFingerprintMetadata :: Request f (Fingerprint -> APIKey -> Ready)
+ Lastfm.Track: getInfo :: ArtistTrackOrMBID t => Request f t
+ Lastfm.Track: getShouts :: ArtistTrackOrMBID t => Request f t
+ Lastfm.Track: getSimilar :: ArtistTrackOrMBID t => Request f t
+ Lastfm.Track: getTags :: ArtistTrackOrMBID t => Request f t
+ Lastfm.Track: getTopFans :: ArtistTrackOrMBID t => Request f t
+ Lastfm.Track: getTopTags :: ArtistTrackOrMBID t => Request f t
+ Lastfm.Track: instance Lastfm.Track.ArtistTrackOrMBID (Lastfm.Request.Artist -> Lastfm.Request.Track -> Lastfm.Request.APIKey -> Lastfm.Internal.Ready)
+ Lastfm.Track: instance Lastfm.Track.ArtistTrackOrMBID (Lastfm.Request.MBID -> Lastfm.Request.APIKey -> Lastfm.Internal.Ready)
+ Lastfm.Track: item :: Request f (Artist -> Track -> Timestamp -> Scrobble)
+ Lastfm.Track: love :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
+ Lastfm.Track: removeTag :: Request f (Artist -> Track -> Tag -> APIKey -> SessionKey -> Sign)
+ Lastfm.Track: scrobble :: NonEmpty (Request f Scrobble) -> Request f (APIKey -> SessionKey -> Sign)
+ Lastfm.Track: search :: Request f (Track -> APIKey -> Ready)
+ Lastfm.Track: share :: Request f (Artist -> Track -> Recipient -> APIKey -> SessionKey -> Sign)
+ Lastfm.Track: unban :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
+ Lastfm.Track: unlove :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
+ Lastfm.Track: updateNowPlaying :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)
+ Lastfm.User: getArtistTracks :: Request f (User -> Artist -> APIKey -> Ready)
+ Lastfm.User: getBannedTracks :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getEvents :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getFriends :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getInfo :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getLovedTracks :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getNeighbours :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getNewReleases :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getPastEvents :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getPersonalTags :: Request f (User -> Tag -> TaggingType -> APIKey -> Ready)
+ Lastfm.User: getPlaylists :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getRecentStations :: Request f (User -> APIKey -> SessionKey -> Sign)
+ Lastfm.User: getRecentTracks :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getRecommendedArtists :: Request f (APIKey -> SessionKey -> Sign)
+ Lastfm.User: getRecommendedEvents :: Request f (APIKey -> SessionKey -> Sign)
+ Lastfm.User: getShouts :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getTopAlbums :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getTopArtists :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getTopTags :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getTopTracks :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getWeeklyAlbumChart :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getWeeklyArtistChart :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getWeeklyChartList :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: getWeeklyTrackChart :: Request f (User -> APIKey -> Ready)
+ Lastfm.User: shout :: Request f (User -> Message -> APIKey -> SessionKey -> Sign)
+ Lastfm.Venue: getEvents :: Request f (Venue -> APIKey -> Ready)
+ Lastfm.Venue: getPastEvents :: Request f (Venue -> APIKey -> Ready)
+ Lastfm.Venue: search :: Request f (VenueName -> APIKey -> Ready)
Files
- CHANGELOG.markdown +11/−0
- README.markdown +4/−4
- example/desktop-authentication.hs +6/−6
- example/liblastfm-examples.cabal +8/−7
- example/mobile-authentication.hs +6/−6
- example/multitag-search.hs +2/−2
- example/playcount.hs +8/−8
- example/recommendations.hs +14/−11
- example/sort-friends.hs +16/−16
- example/web-authentication.hs +2/−2
- liblastfm.cabal +32/−28
- src/Lastfm.hs +14/−0
- src/Lastfm/Album.hs +111/−0
- src/Lastfm/Artist.hs +184/−0
- src/Lastfm/Authentication.hs +66/−0
- src/Lastfm/Chart.hs +69/−0
- src/Lastfm/Event.hs +67/−0
- src/Lastfm/Geo.hs +115/−0
- src/Lastfm/Group.hs +68/−0
- src/Lastfm/Internal.hs +136/−0
- src/Lastfm/Library.hs +112/−0
- src/Lastfm/Playlist.hs +35/−0
- src/Lastfm/Radio.hs +45/−0
- src/Lastfm/Request.hs +430/−0
- src/Lastfm/Response.hs +248/−0
- src/Lastfm/Tag.hs +92/−0
- src/Lastfm/Tasteometer.hs +27/−0
- src/Lastfm/Track.hs +203/−0
- src/Lastfm/User.hs +253/−0
- src/Lastfm/Venue.hs +41/−0
- src/Network/Lastfm.hs +0/−14
- src/Network/Lastfm/Album.hs +0/−117
- src/Network/Lastfm/Artist.hs +0/−198
- src/Network/Lastfm/Authentication.hs +0/−67
- src/Network/Lastfm/Chart.hs +0/−75
- src/Network/Lastfm/Event.hs +0/−70
- src/Network/Lastfm/Geo.hs +0/−126
- src/Network/Lastfm/Group.hs +0/−74
- src/Network/Lastfm/Internal.hs +0/−163
- src/Network/Lastfm/Library.hs +0/−125
- src/Network/Lastfm/Playlist.hs +0/−34
- src/Network/Lastfm/Radio.hs +0/−45
- src/Network/Lastfm/Request.hs +0/−494
- src/Network/Lastfm/Response.hs +0/−249
- src/Network/Lastfm/Tag.hs +0/−101
- src/Network/Lastfm/Tasteometer.hs +0/−25
- src/Network/Lastfm/Track.hs +0/−221
- src/Network/Lastfm/User.hs +0/−275
- src/Network/Lastfm/Venue.hs +0/−41
- test/api/Json/AlbumSpec.hs +2/−2
- test/api/Json/ArtistSpec.hs +2/−2
- test/api/Json/ChartSpec.hs +2/−2
- test/api/Json/EventSpec.hs +2/−2
- test/api/Json/GeoSpec.hs +2/−2
- test/api/Json/GroupSpec.hs +2/−2
- test/api/Json/LibrarySpec.hs +2/−2
- test/api/Json/PlaylistSpec.hs +3/−3
- test/api/Json/TagSpec.hs +2/−2
- test/api/Json/TasteometerSpec.hs +2/−2
- test/api/Json/TrackSpec.hs +2/−2
- test/api/Json/UserSpec.hs +2/−2
- test/api/Json/VenueSpec.hs +2/−2
- test/api/SpecHelper.hs +5/−5
- test/api/Xml/AlbumSpec.hs +2/−2
- test/api/Xml/ArtistSpec.hs +2/−2
- test/api/Xml/ChartSpec.hs +2/−2
- test/api/Xml/EventSpec.hs +2/−2
- test/api/Xml/GeoSpec.hs +2/−2
- test/api/Xml/GroupSpec.hs +2/−2
- test/api/Xml/LibrarySpec.hs +2/−2
- test/api/Xml/PlaylistSpec.hs +4/−4
- test/api/Xml/TagSpec.hs +2/−2
- test/api/Xml/TasteometerSpec.hs +2/−2
- test/api/Xml/TrackSpec.hs +2/−2
- test/api/Xml/UserSpec.hs +2/−2
- test/api/Xml/VenueSpec.hs +2/−2
- test/spec/Lastfm/ResponseSpec.hs +90/−0
- test/spec/Network/Lastfm/ResponseSpec.hs +0/−90
CHANGELOG.markdown view
@@ -1,3 +1,14 @@+0.6.0+=====++ * Stopped using the deprecated stuff from http-client.++ * Removed `N.L.Response.closeConnection` as it's become a no-op.++ * Removed the "Network" prefix from the modules' names.++ * Set default schema to 'http', since ws.audioscrobbler.com has no proper SSL support (issue #27).+ 0.5.1 =====
README.markdown view
@@ -45,10 +45,10 @@ First find it in liblastfm: `Tag` would be the name of the module and `search` would be the name of function. [Here it is][liblastfm/haddocks-usage]. So import a couple of modules: - >>> import Network.Lastfm -- a bunch of useful utilities- >>> import qualified Network.Lastfm.Tag as Tag -- for Tag.search+ >>> import Lastfm -- a bunch of useful utilities+ >>> import qualified Lastfm.Tag as Tag -- for Tag.search -Now you may you applicative `<*>` for required and `<*` or `*>` for optional parameters to construct+Now you may use applicative `<*>` for required and `<*` or `*>` for optional parameters to construct desired request: Tag.search <*> tag "russian-folk" <* limit 3 <*> apiKey "29effec263316a1f8a97f753caaa83e0" <* json@@ -114,7 +114,7 @@ export HASKELL_LIBLASTFM_SECRET="__SECRET__" ``` -Please, consult Lastfm API documentation and `examples/*-authentication.hs`+Please, consult Lastfm API documentation and `example/*-authentication.hs` examples if you don't know where to get your credentials. [last.fm]: http://www.last.fm/
example/desktop-authentication.hs view
@@ -3,12 +3,12 @@ -- -- Please remember to substitute __YOUR_API_KEY__ -- and __YOUR_SECRET__ for real values-import Control.Lens -- lens-import Data.Aeson.Lens -- lens-aeson-import Data.Foldable (for_) -- base-import Data.Text (unpack) -- text-import Network.Lastfm -- liblastfm-import Network.Lastfm.Authentication -- liblastfm+import Control.Lens -- lens+import Data.Aeson.Lens -- lens-aeson+import Data.Foldable (for_) -- base+import Data.Text (unpack) -- text+import Lastfm -- liblastfm+import Lastfm.Authentication -- liblastfm main :: IO ()
example/liblastfm-examples.cabal view
@@ -1,5 +1,5 @@ name: liblastfm-examples-version: 0.5.0+version: 0.6.0 synopsis: Liblastfm examples license: MIT license-file: LICENSE@@ -25,7 +25,7 @@ sort-friends.hs ghc-options: -Wall- -fno-warn-unused-do-bind+ -Werror -threaded executable multitag-search@@ -41,7 +41,7 @@ multitag-search.hs ghc-options: -Wall- -fno-warn-unused-do-bind+ -Werror executable web-authentication default-language:@@ -57,7 +57,7 @@ web-authentication.hs ghc-options: -Wall- -fno-warn-unused-do-bind+ -Werror executable desktop-authentication default-language:@@ -72,7 +72,7 @@ desktop-authentication.hs ghc-options: -Wall- -fno-warn-unused-do-bind+ -Werror executable mobile-authentication default-language:@@ -87,6 +87,7 @@ mobile-authentication.hs ghc-options: -Wall+ -Werror executable playcount default-language:@@ -101,7 +102,7 @@ playcount.hs ghc-options: -Wall- -fno-warn-unused-do-bind+ -Werror executable recommendations default-language:@@ -117,4 +118,4 @@ recommendations.hs ghc-options: -Wall- -fno-warn-unused-do-bind+ -Werror
example/mobile-authentication.hs view
@@ -4,12 +4,12 @@ -- Please remember to substitute __YOUR_API_KEY__, -- __YOUR_SECRET__, __USERNAME__ and __PASSWORD__ -- for real values-import Control.Lens -- lens-import Data.Aeson.Lens -- lens-aeson-import qualified Data.Text as Text -- text-import qualified Data.Text.IO as Text -- text-import Network.Lastfm -- liblastfm-import Network.Lastfm.Authentication -- liblastfm+import Control.Lens -- lens+import Data.Aeson.Lens -- lens-aeson+import qualified Data.Text as Text -- text+import qualified Data.Text.IO as Text -- text+import Lastfm -- liblastfm+import Lastfm.Authentication -- liblastfm main :: IO () main = withConnection $ \conn -> do
example/multitag-search.hs view
@@ -16,8 +16,8 @@ import Data.Text (Text) -- text import qualified Data.Text as Text -- text import qualified Data.Text.IO as Text -- text-import Network.Lastfm -- liblastfm-import qualified Network.Lastfm.Tag as Tag -- liblastfm+import Lastfm -- liblastfm+import qualified Lastfm.Tag as Tag -- liblastfm import System.Environment (getArgs) -- base {-# ANN module ("HLint: ignore Use camelCase" :: String) #-}
example/playcount.hs view
@@ -11,14 +11,14 @@ -- >>> ^C -- $> --}-import Control.Lens -- lens-import Data.Aeson.Lens -- lens-aeson-import Control.Monad -- base-import Data.Text (Text) -- text-import qualified Data.Text.IO as Text -- text-import Network.Lastfm -- liblastfm-import qualified Network.Lastfm.User as User -- liblastfm-import qualified System.IO as IO -- base+import Control.Lens -- lens+import Data.Aeson.Lens -- lens-aeson+import Control.Monad -- base+import Data.Text (Text) -- text+import qualified Data.Text.IO as Text -- text+import Lastfm -- liblastfm+import qualified Lastfm.User as User -- liblastfm+import qualified System.IO as IO -- base main :: IO ()
example/recommendations.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-} {- This example shows how to use signed queries@@ -13,16 +14,18 @@ - Mouth of the Architect -} -import Control.Lens -- lens-import Data.Aeson.Lens -- lens-aeson-import Data.Traversable (for) -- base-import Data.Aeson (Value) -- aeson-import Data.Foldable (Foldable) -- base-import Data.Int (Int64) -- base-import Data.Text (Text) -- text-import qualified Data.Text.IO as Text -- text-import Network.Lastfm -- liblastfm-import qualified Network.Lastfm.User as User -- liblastfm+import Control.Lens -- lens+import Data.Aeson.Lens -- lens-aeson+import Data.Traversable (for) -- base+import Data.Aeson (Value) -- aeson+#if __GLASGOW_HASKELL__ < 710+import Data.Foldable (Foldable) -- base+#endif+import Data.Int (Int64) -- base+import Data.Text (Text) -- text+import qualified Data.Text.IO as Text -- text+import Lastfm -- liblastfm+import qualified Lastfm.User as User -- liblastfm main :: IO ()@@ -31,7 +34,7 @@ mapM_ Text.putStrLn (concat r) -- Construct signed query-query :: Connection -> Request JSON (APIKey -> SessionKey -> Sign) -> IO (Either LastfmError Value)+query :: Connection -> Request 'JSON (APIKey -> SessionKey -> Sign) -> IO (Either LastfmError Value) query conn r = lastfm conn $ sign secret (r <*> ak <*> sk <* json) where ak = apiKey "__YOUR_API_KEY__"
example/sort-friends.hs view
@@ -20,21 +20,21 @@ - Notice: you may want to adjust maximum open files limit - if testing on users with relatively large friends count -}-import Control.Concurrent.Async -- async-import Control.Lens -- lens-import Data.Aeson.Lens -- lens-aeson-import Data.Aeson (Value) -- aeson-import Data.Function (on) -- base-import Data.List (sortBy) -- base-import Data.Maybe (fromMaybe) -- base-import Data.Monoid ((<>)) -- base-import Data.Text (Text) -- text-import Data.Text.Lens (unpacked) -- lens-import qualified Data.Text.IO as Text -- text-import Network.Lastfm hiding (to) -- liblastfm-import qualified Network.Lastfm.User as User -- liblastfm-import qualified Network.Lastfm.Tasteometer as Tasteometer -- liblastfm-import Text.Read (readMaybe) -- base+import Control.Concurrent.Async -- async+import Control.Lens -- lens+import Data.Aeson.Lens -- lens-aeson+import Data.Aeson (Value) -- aeson+import Data.Function (on) -- base+import Data.List (sortBy) -- base+import Data.Maybe (fromMaybe) -- base+import Data.Monoid ((<>)) -- base+import Data.Text (Text) -- text+import Data.Text.Lens (unpacked) -- lens+import qualified Data.Text.IO as Text -- text+import Lastfm hiding (to) -- liblastfm+import qualified Lastfm.User as User -- liblastfm+import qualified Lastfm.Tasteometer as Tasteometer -- liblastfm+import Text.Read (readMaybe) -- base type Score = Text@@ -67,7 +67,7 @@ pretty :: [(Text, Score)] -> IO () pretty = mapM_ (\(n,s) -> Text.putStrLn $ n <> ": " <> s) . take 5 . sortBy (flip compare `on` snd) -query :: Connection -> Request JSON (APIKey -> Ready) -> IO (Either LastfmError Value)+query :: Connection -> Request 'JSON (APIKey -> Ready) -> IO (Either LastfmError Value) query conn r = lastfm conn (r <*> apiKey "234fc6e0f41f6ef99b7bd62ebaf8d318" <* json) target :: Text
example/web-authentication.hs view
@@ -9,8 +9,8 @@ import Control.Monad.IO.Class (liftIO) -- transformers import Data.IORef -- base import Happstack.Server -- happstack-server-import Network.Lastfm -- liblastfm-import Network.Lastfm.Authentication -- liblastfm+import Lastfm -- liblastfm+import Lastfm.Authentication -- liblastfm main :: IO ()
liblastfm.cabal view
@@ -1,5 +1,5 @@ name: liblastfm-version: 0.5.1+version: 0.6.0 synopsis: Lastfm API interface license: MIT license-file: LICENSE@@ -27,6 +27,12 @@ type: git location: https://github.com/supki/liblastfm +source-repository this+ type: git+ location: https://github.com/supki/liblastfm+ tag: 0.6.0++ flag test-api description: a real API test default: False@@ -41,40 +47,39 @@ , bytestring , cereal , containers >= 0.5- , crypto-api- , http-client >= 0.3+ , cryptonite >= 0.7+ , http-client >= 0.4.19 , http-client-tls >= 0.2 , network-uri , profunctors- , pureMD5 , semigroups+ , transformers , text , xml-conduit >= 1.1 hs-source-dirs: src exposed-modules:- Network.Lastfm- Network.Lastfm.Album- Network.Lastfm.Artist- Network.Lastfm.Authentication- Network.Lastfm.Chart- Network.Lastfm.Event- Network.Lastfm.Geo- Network.Lastfm.Group- Network.Lastfm.Internal- Network.Lastfm.Library- Network.Lastfm.Playlist- Network.Lastfm.Radio- Network.Lastfm.Request- Network.Lastfm.Response- Network.Lastfm.Tag- Network.Lastfm.Tasteometer- Network.Lastfm.Track- Network.Lastfm.User- Network.Lastfm.Venue+ Lastfm+ Lastfm.Album+ Lastfm.Artist+ Lastfm.Authentication+ Lastfm.Chart+ Lastfm.Event+ Lastfm.Geo+ Lastfm.Group+ Lastfm.Internal+ Lastfm.Library+ Lastfm.Playlist+ Lastfm.Radio+ Lastfm.Request+ Lastfm.Response+ Lastfm.Tag+ Lastfm.Tasteometer+ Lastfm.Track+ Lastfm.User+ Lastfm.Venue ghc-options: -Wall- -fno-warn-unused-do-bind -funbox-strict-fields test-suite api@@ -132,7 +137,6 @@ ghc-options: -Wall -fno-warn-orphans- -fno-warn-unused-do-bind test-suite spec default-language:@@ -145,7 +149,7 @@ , bytestring , cereal , containers- , crypto-api+ , cryptonite , hspec , hspec-expectations-lens >= 0.3.0.0 , http-client@@ -154,8 +158,8 @@ , lens-aeson >= 1.0.0.1 , network-uri , profunctors- , pureMD5 , text+ , transformers , xml-conduit , xml-html-conduit-lens >= 0.3 hs-source-dirs:@@ -164,7 +168,7 @@ main-is: Spec.hs other-modules:- Network.Lastfm.ResponseSpec+ Lastfm.ResponseSpec ghc-options: -Wall cpp-options:
+ src/Lastfm.hs view
@@ -0,0 +1,14 @@+-- | Lastfm API interface+module Lastfm+ ( -- * Utilities for constructing requests+ module Lastfm.Request+ -- * Utilities for signing and sending requests+ , module Lastfm.Response+ -- * Control.Applicative is re-exported for convenience+ , module Control.Applicative+ ) where++import Control.Applicative++import Lastfm.Request+import Lastfm.Response
+ src/Lastfm/Album.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm album API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Album as Album+-- @+module Lastfm.Album+ ( ArtistAlbumOrMBID+ , addTags, getBuyLinks, getInfo, getShouts+ , getTags, getTopTags, removeTag, search, share+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif++import Lastfm.Request+++-- | Unify ('Artist' -> 'Album' -> …) and ('MBID' -> …)+class ArtistAlbumOrMBID r a | a -> r++instance ArtistAlbumOrMBID r (MBID -> APIKey -> r)+instance ArtistAlbumOrMBID r (Artist -> Album -> APIKey -> r)+++-- | Tag an album using a list of user supplied tags.+--+-- <http://www.last.fm/api/show/album.addTags>+addTags :: Request f (Artist -> Album -> [Tag] -> APIKey -> SessionKey -> Sign)+addTags = api "album.addTags" <* post+++-- | Get a list of Buy Links for a particular Album. It is+-- required that you supply either the artist and track params or the mbid parameter.+--+-- Optional: 'autocorrect'+--+-- <http://www.last.fm/api/show/album.getBuylinks>+getBuyLinks :: ArtistAlbumOrMBID Ready a => Request f (Country -> a)+getBuyLinks = api "album.getBuyLinks"+++-- | Get the metadata for an album on Last.fm using the album name or+-- a musicbrainz id. See playlist.fetch on how to get the album playlist.+--+-- Optional: 'autocorrect', 'username', 'language'+--+-- <http://www.last.fm/api/show/album.getInfo>+getInfo :: ArtistAlbumOrMBID Ready a => Request f a+getInfo = api "album.getInfo"+++-- | Get shouts for this album.+--+-- Optional: 'autocorrect', 'limit', 'page'+--+-- <http://www.last.fm/api/show/album.getShouts>+getShouts :: ArtistAlbumOrMBID Ready a => Request f a+getShouts = api "album.getShouts"+++-- | Get the tags applied by an individual user to an album on Last.fm.+--+-- Optional: 'autocorrect', 'user'+--+-- <http://www.last.fm/api/show/album.getTags>+getTags :: ArtistAlbumOrMBID r a => Request f a+getTags = api "album.getTags"+++-- | Get the top tags for an album on Last.fm, ordered by popularity.+--+-- Optional: 'autocorrect'+--+-- <http://www.last.fm/api/show/album.getTopTags>+getTopTags :: ArtistAlbumOrMBID Ready a => Request f a+getTopTags = api "album.getTopTags"+++-- | Remove a user's tag from an album.+--+-- <http://www.last.fm/api/show/album.removeTag>+removeTag :: Request f (Artist -> Album -> Tag -> APIKey -> SessionKey -> Sign)+removeTag = api "album.removeTag" <* post+++-- | Search for an album by name. Returns album matches sorted by relevance.+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/album.search>+search :: Request f (Album -> APIKey -> Ready)+search = api "album.search"+++-- | Share an album with one or more Last.fm users or other friends.+--+-- Optional: 'public', 'message', 'recipient'+--+-- <http://www.last.fm/api/show/album.share>+share :: Request f (Album -> Artist -> Recipient -> APIKey -> SessionKey -> Sign)+share = api "album.share" <* post
+ src/Lastfm/Artist.hs view
@@ -0,0 +1,184 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm artist API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Artist as Artist+-- @+module Lastfm.Artist+ ( ArtistOrMBID+ , addTags, getCorrection, getEvents, getInfo+ , getPastEvents, getPodcast, getShouts+ , getSimilar, getTags, getTopAlbums+ , getTopFans, getTopTags, getTopTracks+ , removeTag, search, share, shout+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif++import Lastfm.Request++-- | Unify ('Artist' -> …) and ('MBID' -> …)+class ArtistOrMBID r a++instance ArtistOrMBID r MBID+instance ArtistOrMBID r Artist+++-- | Tag an artist with one or more user supplied tags.+--+-- <http://www.last.fm/api/show/artist.addTags>+addTags :: Request f (Artist -> [Tag] -> APIKey -> SessionKey -> Sign)+addTags = api "artist.addTags" <* post+++-- | Use the last.fm corrections data to check whether the+-- supplied artist has a correction to a canonical artist+--+-- <http://www.last.fm/api/show/artist.getCorrection>+getCorrection :: Request f (Artist -> APIKey -> Ready)+getCorrection = api "artist.getCorrection"+++-- | Get a list of upcoming events for this artist. Easily+-- integratable into calendars, using the ical standard (see feeds section below).+--+-- Optional: 'autocorrect', 'limit', 'pages', 'festivalsonly'+--+-- <http://www.last.fm/api/show/artist.getEvents>+getEvents :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)+getEvents = api "artist.getEvents"+++-- | Get the metadata for an artist. Includes biography.+--+-- Optional: 'language', 'autocorrect', 'username'+--+-- <http://www.last.fm/api/show/artist.getInfo>+getInfo :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)+getInfo = api "artist.getInfo"+++-- | Get a paginated list of all the events this artist has played at in the past.+--+-- Optional: 'page', 'autocorrect', 'limit'+--+-- <http://www.last.fm/api/show/artist.getPastEvents>+getPastEvents :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)+getPastEvents = api "artist.getPastEvents"+++-- | Get a podcast of free mp3s based on an artist+--+-- Optional: 'autocorrect'+--+-- <http://www.last.fm/api/show/artist.getPodcast>+getPodcast :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)+getPodcast = api "artist.getPodcast"+++-- | Get shouts for this artist. Also available as an rss feed.+--+-- Optional:'autocorrect', 'limit', 'page'+--+-- <http://www.last.fm/api/show/artist.getShouts>+getShouts :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)+getShouts = api "artist.getShouts"+++-- | Get all the artists similar to this artist+--+-- Optional: 'limit', 'autocorrect'+--+-- <http://www.last.fm/api/show/artist.getSimilar>+getSimilar :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)+getSimilar = api "artist.getSimilar"+++-- | Get the tags applied by an individual user to an artist on Last.fm.+-- If accessed as an authenticated service /and/ you don't supply a+-- user parameter then this service will return tags for+-- the authenticated user. To retrieve the list of top tags applied+-- to an artist by all users use 'Lastfm.Artist.getTopTags'.+--+-- Optional: 'user', 'autocorrect'+--+-- <http://www.last.fm/api/show/artist.getTags>+getTags :: ArtistOrMBID r a => Request f (a -> APIKey -> r)+getTags = api "artist.getTags"+++-- | Get the top albums for an artist on Last.fm, ordered by popularity.+--+-- Optional: 'autocorrect', 'page', 'limit'+--+-- <http://www.last.fm/api/show/artist.getTopAlbums>+getTopAlbums :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)+getTopAlbums = api "artist.getTopAlbums"+++-- | Get the top fans for an artist on Last.fm, based on listening data.+--+-- Optional: 'autocorrect'+--+-- <http://www.last.fm/api/show/artist.getTopFans>+getTopFans :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)+getTopFans = api "artist.getTopFans"+++-- | Get the top tags for an artist on Last.fm, ordered by popularity.+--+-- Optional: 'autocorrect'+--+-- <http://www.last.fm/api/show/artist.getTopTags>+getTopTags :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)+getTopTags = api "artist.getTopTags"+++-- | Get the top tracks by an artist on Last.fm, ordered by popularity+--+-- Optional: 'autocorrect', 'page', 'limit'+--+-- <http://www.last.fm/api/show/artist.getTopTracks>+getTopTracks :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)+getTopTracks = api "artist.getTopTracks"+++-- | Remove a user's tag from an artist.+--+-- <http://www.last.fm/api/show/artist.removeTag>+removeTag :: Request f (Artist -> Tag -> APIKey -> SessionKey -> Sign)+removeTag = api "artist.removeTag" <* post+++-- | Search for an artist by name. Returns artist matches sorted by relevance.+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/artist.search>+search :: Request f (Artist -> APIKey -> Ready)+search = api "artist.search"+++-- | Share an artist with Last.fm users or other friends.+--+-- Optional: 'message', 'public'+--+-- <http://www.last.fm/api/show/artist.share>+share :: Request f (Artist -> Recipient -> APIKey -> SessionKey -> Sign)+share = api "artist.share" <* post+++-- | Shout in this artist's shoutbox+--+-- <http://www.last.fm/api/show/artist.shout>+shout :: Request f (Artist -> Message -> APIKey -> SessionKey -> Sign)+shout = api "artist.shout" <* post
+ src/Lastfm/Authentication.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm authentication procedure helpers+--+-- Basically, lastfm provides 3 ways to authenticate user:+--+-- - web application - <http://www.last.fm/api/webauth>+--+-- - desktop application - <http://www.last.fm/api/desktopauth>+--+-- - modile application - <http://www.last.fm/api/mobileauth>+--+-- Note that you can use any of them in your+-- application despite their names+--+-- How to get session key for yourself for debug with GHCi:+--+-- >>> import Lastfm+-- >>> import Lastfm.Authentication+-- >>> :set -XOverloadedStrings+-- >>> con <- newConnection+-- >>> lastfm con $ getToken <*> apiKey "__API_KEY__" <* json+-- Right (Object (fromList [("token",String "__TOKEN__")]))+-- >>> putStrLn . link $ apiKey "__API_KEY__" <* token "__TOKEN__"+-- http://www.last.fm/api/auth/?api_key=__API_KEY__&token=__TOKEN__+-- >>> -- Click that link ^^^+-- >>> lastfm con $ sign "__SECRET__" $ getSession <*> token "__TOKEN__" <*> apiKey "__API_KEY__" <* json+-- Right (Object (fromList [("session",Object (fromList [("subscriber",String "0"),("key",String "__SESSION_KEY__"),("name",String "__USER__")]))]))+module Lastfm.Authentication+ ( -- * Helpers+ getToken, getSession, getMobileSession+ , link+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+import Data.Monoid+#endif++import Lastfm.Internal+import Lastfm.Request+++-- | Get authorization token+getToken :: Request f (APIKey -> Ready)+getToken = api "auth.getToken"+++-- | Get session key+getMobileSession :: Request f (Username -> Password -> APIKey -> Sign)+getMobileSession = api "auth.getMobileSession" <* post+++-- | Get session key+getSession :: Request f (Token -> APIKey -> Sign)+getSession = api "auth.getSession"+++-- | Construct link user should follow to approve application+link :: Request f a -> String+link q = render . unwrap q $ R+ { _host = "http://www.last.fm/api/auth/"+ , _method = mempty+ , _query = mempty+ }
+ src/Lastfm/Chart.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm chart API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Chart as Chart+-- @+module Lastfm.Chart+ ( getHypedArtists, getHypedTracks, getLovedTracks+ , getTopArtists, getTopTags, getTopTracks+ ) where++import Lastfm.Request+++-- | Get the hyped artists chart+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/chart.getHypedArtists>+getHypedArtists :: Request f (APIKey -> Ready)+getHypedArtists = api "chart.getHypedArtists"+++-- | Get the top artists chart+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/chart.getHypedTracks>+getHypedTracks :: Request f (APIKey -> Ready)+getHypedTracks = api "chart.getHypedTracks"+++-- | Get the most loved tracks chart+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/chart.getLovedTracks>+getLovedTracks :: Request f (APIKey -> Ready)+getLovedTracks = api "chart.getLovedTracks"+++-- | Get the top artists chart+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/chart.getTopArtists>+getTopArtists :: Request f (APIKey -> Ready)+getTopArtists = api "chart.getTopArtists"+++-- | Get the top artists chart+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/chart.getTopTags>+getTopTags :: Request f (APIKey -> Ready)+getTopTags = api "chart.getTopTags"+++-- | Get the top tracks chart+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/chart.getTopTracks>+getTopTracks :: Request f (APIKey -> Ready)+getTopTracks = api "chart.getTopTracks"
+ src/Lastfm/Event.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm event API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Event as Event+-- @+module Lastfm.Event+ ( attend, getAttendees, getInfo, getShouts, share, shout+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif++import Lastfm.Request+++-- | Set a user's attendance status for an event.+--+-- <http://www.last.fm/api/show/event.attend>+attend :: Request f (Event -> Status -> APIKey -> SessionKey -> Sign)+attend = api "event.attend" <* post+++-- | Get a list of attendees for an event.+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/event.getAttendees>+getAttendees :: Request f (Event -> APIKey -> Ready)+getAttendees = api "event.getAttendees"+++-- | Get the metadata for an event on Last.fm. Includes attendance and lineup information.+--+-- <http://www.last.fm/api/show/event.getInfo>+getInfo :: Request f (Event -> APIKey -> Ready)+getInfo = api "event.getInfo"+++-- | Get shouts for this event. Also available as an rss feed.+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/event.getShouts>+getShouts :: Request f (Event -> APIKey -> Ready)+getShouts = api "event.getShouts"+++-- | Share an event with one or more Last.fm users or other friends.+--+-- Optional: 'public', 'message'+--+-- <http://www.last.fm/api/show/event.share>+share :: Request f (Event -> Recipient -> APIKey -> SessionKey -> Sign)+share = api "event.share" <* post+++-- | Shout in this event's shoutbox+--+-- <http://www.last.fm/api/show/event.shout>+shout :: Request f (Event -> Message -> APIKey -> SessionKey -> Sign)+shout = api "event.shout" <* post
+ src/Lastfm/Geo.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm geo API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Geo as Geo+-- @+module Lastfm.Geo+ ( getEvents, getMetroArtistChart, getMetroHypeArtistChart+ , getMetroHypeTrackChart, getMetroTrackChart, getMetroUniqueArtistChart+ , getMetroUniqueTrackChart, getMetroWeeklyChartlist, getMetros+ , getTopArtists, getTopTracks+ ) where++import Lastfm.Request+++-- | Get all events in a specific location by country or city name.+--+-- Optional: 'longitude', 'latitude', 'location', 'distance', 'page', 'tag', 'festivalsonly', 'limit'+--+-- <http://www.last.fm/api/show/geo.getEvents>+getEvents :: Request f (APIKey -> Ready)+getEvents = api "geo.getEvents"+++-- | Get a chart of artists for a metro+--+-- Optional: 'start', 'end', 'page', 'limit'+--+-- <http://www.last.fm/api/show/geo.getMetroArtistChart>+getMetroArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)+getMetroArtistChart = api "geo.getMetroArtistChart"+++-- | Get a chart of hyped (up and coming) artists for a metro+--+-- Optional: 'start', 'end', 'page', 'limit'+--+-- <http://www.last.fm/api/show/geo.getMetroHypeArtistChart>+getMetroHypeArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)+getMetroHypeArtistChart = api "geo.getMetroHypeArtistChart"+++-- | Get a chart of tracks for a metro+--+-- Optional: 'start', 'end', 'page', 'limit'+--+-- <http://www.last.fm/api/show/geo.getMetroHypeTrackChart>+getMetroHypeTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)+getMetroHypeTrackChart = api "geo.getMetroHypeTrackChart"+++-- | Get a chart of tracks for a metro+--+-- Optional: 'start', 'end', 'page', 'limit'+--+-- <http://www.last.fm/api/show/geo.getMetroTrackChart>+getMetroTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)+getMetroTrackChart = api "geo.getMetroTrackChart"+++-- | Get a chart of the artists which make that metro unique+--+-- Optional: 'start', 'end', 'page', 'limit'+--+-- <http://www.last.fm/api/show/geo.getMetroUniqueArtistChart>+getMetroUniqueArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)+getMetroUniqueArtistChart = api "geo.getMetroUniqueArtistChart"+++-- | Get a chart of tracks for a metro+--+-- Optional: 'start', 'end', 'page', 'limit'+--+-- <http://www.last.fm/api/show/geo.getMetroUniqueTrackChart>+getMetroUniqueTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)+getMetroUniqueTrackChart = api "geo.getMetroUniqueTrackChart"+++-- | Get a list of available chart periods for this metro,+-- expressed as date ranges which can be sent to the chart services.+--+-- <http://www.last.fm/api/show/geo.getMetroWeeklyChartlist>+getMetroWeeklyChartlist :: Request f (Metro -> APIKey -> Ready)+getMetroWeeklyChartlist = api "geo.getMetroWeeklyChartlist"+++-- | Get a list of valid countries and metros for use in the other webservices+--+-- Optional: 'country'+--+-- <http://www.last.fm/api/show/geo.getMetros>+getMetros :: Request f (APIKey -> Ready)+getMetros = api "geo.getMetros"+++-- | Get the most popular artists on Last.fm by country+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/geo.getTopArtists>+getTopArtists :: Request f (Country -> APIKey -> Ready)+getTopArtists = api "geo.getTopArtists"+++-- | Get the most popular tracks on Last.fm last week by country+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/geo.getTopTracks>+getTopTracks :: Request f (Country -> APIKey -> Ready)+getTopTracks = api "geo.getTopTracks"
+ src/Lastfm/Group.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm group API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Group as Group+-- @+module Lastfm.Group+ ( getHype, getMembers, getWeeklyAlbumChart, getWeeklyArtistChart, getWeeklyChartList, getWeeklyTrackChart+ ) where++import Lastfm.Request+++-- | Get the hype list for a group+--+-- <http://www.last.fm/api/show/group.getHype>+getHype :: Request f (Group -> APIKey -> Ready)+getHype = api "group.getHype"+++-- | Get a list of members for this group.+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/group.getMembers>+getMembers :: Request f (Group -> APIKey -> Ready)+getMembers = api "group.getMembers"+++-- | Get an album chart for a group, for a given date range.+-- If no date range is supplied, it will return the most recent album chart for this group.+--+-- Optional: 'from', 'to'+--+-- <http://www.last.fm/api/show/group.getWeeklyAlbumChart>+getWeeklyAlbumChart :: Request f (Group -> APIKey -> Ready)+getWeeklyAlbumChart = api "group.getWeeklyAlbumChart"+++-- | Get an artist chart for a group, for a given date range.+-- If no date range is supplied, it will return the most recent album chart for this group.+--+-- Optional: 'from', 'to'+--+-- <http://www.last.fm/api/show/group.getWeeklyArtistChart>+getWeeklyArtistChart :: Request f (Group -> APIKey -> Ready)+getWeeklyArtistChart = api "group.getWeeklyArtistChart"+++-- | Get a list of available charts for this group, expressed as+-- date ranges which can be sent to the chart services.+--+-- <http://www.last.fm/api/show/group.getWeeklyChartList>+getWeeklyChartList :: Request f (Group -> APIKey -> Ready)+getWeeklyChartList = api "group.getWeeklyChartList"+++-- | Get a track chart for a group, for a given date range.+-- If no date range is supplied, it will return the most recent album chart for this group.+--+-- Optional: 'from', 'to'+--+-- <http://www.last.fm/api/show/group.getWeeklyTrackChart>+getWeeklyTrackChart :: Request f (Group -> APIKey -> Ready)+getWeeklyTrackChart = api "group.getWeeklyTrackChart"
+ src/Lastfm/Internal.hs view
@@ -0,0 +1,136 @@+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 708+{-# LANGUAGE AutoDeriveTypeable #-}+#endif+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE KindSignatures #-}+-- | liblastfm internals+--+-- You shouldn't need to import this module unless you are doing something interesting.+module Lastfm.Internal+ ( Request(..)+ , Format(..)+ , Ready+ , Sign+ , R(..)+ , wrap+ , unwrap+ , render+ , coerce+ , absorbQuery+ , indexedWith+ ) where++import Control.Applicative+import Data.ByteString (ByteString)+#if __GLASGOW_HASKELL__ < 710+import Data.Foldable (Foldable(..))+#endif+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as M+import Data.Monoid+import Data.Serialize (Serialize(..))+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+#if __GLASGOW_HASKELL__ < 710+import Data.Traversable (Traversable(..))+#endif+import Network.URI (escapeURIChar, isUnreserved)+++-- | Lastfm API request data type+--+-- low-level representation+data R (f :: Format) = R+ { _host :: {-# UNPACK #-} !Text+ , _method :: {-# UNPACK #-} !ByteString+ , _query :: !(Map Text Text)+ }++-- | Response format: either JSON or XML+data Format = JSON | XML++-- | Request that is ready to be sent+data Ready++-- | Request that requires signing procedure+data Sign+++-- | Lastfm API request data type+--+-- @a@ is the authentication state. Can be 'Ready', which means this 'Request' is+-- ready to be sent, or 'Sign', if the request signature hasn't been computed yet+--+-- @f@ is the response format (liblastfm supports both 'JSON' and 'XML')+newtype Request f a = Request { unRequest :: Const (Dual (Endo (R f))) a }++instance Functor (Request f) where+ fmap f (Request x) = Request (fmap f x)++instance Applicative (Request f) where+ pure x = Request (pure x)+ Request f <*> Request x = Request (f <*> x)++instance Foldable (Request f) where+ foldMap _ (Request _) = mempty -- not sure why this instance isn't in base++instance Traversable (Request f) where+ traverse _ (Request (Const x)) = pure (Request (Const x)) -- and that+++coerce :: Request f a -> Request f b+coerce (Request (Const x)) = Request (Const x)+++-- | Construct String from request for networking+render :: R f -> String+render R { _host = h, _query = q } =+ T.unpack $ mconcat [h, "?", argie q]+ where+ argie = T.intercalate "&" . M.foldrWithKey (\k v m -> T.concat [escape k, "=", escape v] : m) []++ escape = T.concatMap (T.pack . escapeURIChar isUnreserved)+++-- | Wrapping to interesting 'Monoid' ('R' -> 'R') instance+wrap :: (R f -> R f) -> Request f a+wrap = Request . Const . Dual . Endo++-- | Unwrapping from interesting 'Monoid' ('R' -> 'R') instance+unwrap :: Request f a -> R f -> R f+unwrap = appEndo . getDual . getConst . unRequest+++-- | Absorbing a bunch of queries, useful in batch operations+absorbQuery :: Foldable t => t (Request f b) -> Request f a+absorbQuery rs = wrap $ \r ->+ r { _query = _query r <> foldMap (_query . ($ rempty) . unwrap) rs }++-- | Transforming Request to the "array notation"+indexedWith :: Int -> Request f a -> Request f a+indexedWith n r = r <* wrap (\s ->+ s { _query = M.mapKeys (\k -> k <> "[" <> T.pack (show n) <> "]") (_query s) })++-- | Empty request+rempty :: R f+rempty = R mempty mempty mempty+++-- Miscellaneous instances++instance Serialize (R f) where+ put r = do+ put $ T.encodeUtf8 (_host r)+ put $ _method r+ put $ bimap T.encodeUtf8 T.encodeUtf8 (_query r)+ get = do+ h <- T.decodeUtf8 <$> get+ m <- get+ q <- bimap T.decodeUtf8 T.decodeUtf8 <$> get+ return R { _host = h, _method = m, _query = q }++bimap :: (Ord s, Ord t) => (s -> t) -> (a -> b) -> Map s a -> Map t b+bimap f g = M.mapKeys f . M.map g
+ src/Lastfm/Library.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm library API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Library as Library+-- @+module Lastfm.Library+ ( addAlbum, albumItem, addArtist, artistItem, addTrack+ , getAlbums, getArtists, getTracks+ , removeAlbum, removeArtist, removeScrobble, removeTrack+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as N++import Lastfm.Internal (absorbQuery, indexedWith, wrap)+import Lastfm.Request+++-- | Add an album or collection of albums to a user's Last.fm library+--+-- <http://www.last.fm/api/show/library.addAlbum>+addAlbum :: NonEmpty (Request f LibraryAlbum) -> Request f (APIKey -> SessionKey -> Sign)+addAlbum batch = api "library.addAlbum" <* items <* post+ where+ items = absorbQuery (N.zipWith indexedWith (N.fromList [0..]) batch)++-- | What artist to add to library?+albumItem :: Request f (Artist -> Album -> LibraryAlbum)+albumItem = wrap id+++-- | Add an artist to a user's Last.fm library+--+-- <http://www.last.fm/api/show/library.addArtist>+addArtist :: NonEmpty (Request f LibraryArtist) -> Request f (APIKey -> SessionKey -> Sign)+addArtist batch = api "library.addArtist" <* items <* post+ where+ items = absorbQuery (N.zipWith indexedWith (N.fromList [0..]) batch)++-- | What album to add to library?+artistItem :: Request f (Artist -> LibraryArtist)+artistItem = wrap id+++-- | Add a track to a user's Last.fm library+--+-- <http://www.last.fm/api/show/library.addTrack>+addTrack :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)+addTrack = api "library.addTrack" <* post+++-- | A paginated list of all the albums in a user's library, with play counts and tag counts.+--+-- Optional: 'artist', 'limit', 'page'+--+-- <http://www.last.fm/api/show/library.getAlbums>+getAlbums :: Request f (User -> APIKey -> Ready)+getAlbums = api "library.getAlbums"+++-- | A paginated list of all the artists in a user's library, with play counts and tag counts.+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/library.getArtists>+getArtists :: Request f (User -> APIKey -> Ready)+getArtists = api "library.getArtists"+++-- | A paginated list of all the tracks in a user's library, with play counts and tag counts.+--+-- Optional: 'artist', 'album', 'page', 'limit'+--+-- <http://www.last.fm/api/show/library.getTracks>+getTracks :: Request f (User -> APIKey -> Ready)+getTracks = api "library.getTracks"+++-- | Remove an album from a user's Last.fm library+--+-- <http://www.last.fm/api/show/library.removeAlbum>+removeAlbum :: Request f (Artist -> Album -> APIKey -> SessionKey -> Sign)+removeAlbum = api "library.removeAlbum" <* post+++-- | Remove an artist from a user's Last.fm library+--+-- <http://www.last.fm/api/show/library.removeArtist>+removeArtist :: Request f (Artist -> APIKey -> SessionKey -> Sign)+removeArtist = api "library.removeArtist" <* post+++-- | Remove a scrobble from a user's Last.fm library+--+-- <http://www.last.fm/api/show/library.removeScrobble>+removeScrobble :: Request f (Artist -> Track -> Timestamp -> APIKey -> SessionKey -> Sign)+removeScrobble = api "library.removeScrobble" <* post+++-- | Remove a track from a user's Last.fm library+--+-- <http://www.last.fm/api/show/library.removeTrack>+removeTrack :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)+removeTrack = api "library.removeTrack" <* post
+ src/Lastfm/Playlist.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm playlist API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Playlist as Playlist+-- @+module Lastfm.Playlist+ ( addTrack, create+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif++import Lastfm.Request+++-- | Add a track to a Last.fm user's playlist+--+-- <http://www.last.fm/api/show/playlist.addTrack>+addTrack :: Request f (Playlist -> Artist -> Track -> APIKey -> SessionKey -> Sign)+addTrack = api "playlist.addTrack" <* post+++-- | Create a Last.fm playlist on behalf of a user+--+-- Optional: 'title', 'description'+--+-- <http://www.last.fm/api/show/playlist.create>+create :: Request f (APIKey -> SessionKey -> Sign)+create = api "playlist.create" <* post
+ src/Lastfm/Radio.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm radio API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Radio as Radio+-- @+module Lastfm.Radio+ ( getPlaylist, search, tune+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif++import Lastfm.Request+++-- | Fetch new radio content periodically in an XSPF format.+--+-- Optional: 'discovery', 'rtp', 'buyLinks'+--+-- <http://www.last.fm/api/show/radio.getPlaylist>+getPlaylist :: Request f (Multiplier -> Bitrate -> APIKey -> SessionKey -> Sign)+getPlaylist = api "radio.getPlaylist"+++-- | Resolve the name of a resource into a station depending on which resource+-- it is most likely to represent.+--+-- <http://www.last.fm/api/show/radio.search>+search :: Request f (Name -> APIKey -> Ready)+search = api "radio.search"+++-- | Tune in to a Last.fm radio station.+--+-- Optional: 'language'+--+-- <http://www.last.fm/api/show/radio.tune>+tune :: Request f (Station -> APIKey -> SessionKey -> Sign)+tune = api "radio.tune" <* post
+ src/Lastfm/Request.hs view
@@ -0,0 +1,430 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Request construction+module Lastfm.Request+ ( -- * Request+ Request, R, Ready, Sign, Format(..)+ -- * Request major parameters+ , api, https, post, get, json, xml, APIKey, apiKey, SessionKey, sessionKey+ -- * Request minor parameters+ , Token, token, Callback, callback+ , Artist, artist, artists, Album, album, MBID, mbid+ , Country, country, Autocorrect, autocorrect+ , Event, event, Status(..), status+ , From, from, To, to, Group, group+ , Language, language, Distance, distance+ , Longitude, longitude, Latitude, latitude, Location, location+ , Start, start, End, end, Festivals, festivalsonly+ , StartTimestamp, startTimestamp, EndTimestamp, endTimestamp+ , Metro, metro, Tag, tags, tag, Track, track, Timestamp , timestamp, Fingerprint, fingerprint+ , AlbumArtist, albumArtist, Duration, duration, TrackNumber, trackNumber+ , Playlist, playlist, Title, title, Description, description+ , ChosenByUser, chosenByUser, Context, context, StreamId, streamId+ , RecentTracks, recentTracks+ , Recipient, recipient, Username, username, User, user, Password, password+ , Public, public, Message, message, Page, page, Limit, limit+ , TaggingType, taggingType, UseRecs, useRecs, Venue, venue, VenueName, venueName+ , Discovery, discovery, RTP, rtp, BuyLinks, buyLinks, Multiplier(..), multiplier+ , Bitrate(..), bitrate, Name, name, Station, station+ , Targeted, comparison, Scrobble, LibraryAlbum, LibraryArtist+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif+import Data.Int (Int64)+#if __GLASGOW_HASKELL__ < 710+import Data.Monoid (mempty)+#endif+import Data.Monoid ((<>))+import qualified Data.Map.Strict as M+import Data.Text (Text)+import qualified Data.Text as T++import Lastfm.Internal+++class Argument a where+ add :: Text -> a -> Request f b+ add k v = wrap $ \r@R { _query = q } -> r { _query = M.insert k (toText v) q }++ toText :: a -> Text++instance Argument Text where+ toText = id++instance Argument Bool where+ toText b = if b then "1" else "0"++instance Argument Int64 where+ toText = T.pack . show++instance Argument a => Argument [a] where+ toText = T.intercalate "," . map toText+++data APIKey+data SessionKey+data Token+data Callback+++-- | Change request API method+--+-- Primarily used in API call wrappers, not intended for usage by library user+api :: Text -> Request f a+api = add "method"++-- | Change API scheme to https ('http' is the default one)+https :: Request f a+https = wrap $ \r -> r { _host = "https://ws.audioscrobbler.com/2.0/" }++-- | Change html _method to GET+--+-- Primarily used in API call wrappers, not intended for usage by library user+get :: Request f a+get = wrap $ \r -> r { _method = "GET" }++-- | Change html _method to POST+--+-- Primarily used in API call wrappers, not intended for usage by library user+post :: Request f a+post = wrap $ \r -> r { _method = "POST" }++-- | Change API response format to JSON+--+-- This is a little helper. It's actually enough+-- to specialize Format manually+json :: Request 'JSON a+json = wrap id++-- | Change API response format to XML+--+-- This is a little helper. It's actually enough+-- to specialize Format manually+xml :: Request 'XML a+xml = wrap id++-- | Change request API key+apiKey :: Text -> Request f APIKey+apiKey = add "api_key"++-- | Change request session key+sessionKey :: Text -> Request f SessionKey+sessionKey = add "sk"++-- | Add token parameter+token :: Text -> Request f Token+token = add "token"++-- | Add callback link parameter+callback :: Text -> Request f Callback+callback = add "cb"+++data Artist+data Album+data MBID+data Country+data Language+data Tag+data Autocorrect+data Page+data Limit+data Message+data Public+data Recipient+data Username+data User+data Password+data Status = Attending | Maybe | NotAttending+data Event+data Festivals+data Longitude+data Latitude+data Location+data Distance+data Metro+data Start+data End+data StartTimestamp+data EndTimestamp+data From+data To+data Playlist+data Title+data Description+data Track+data Timestamp+data Fingerprint+data AlbumArtist+data Context+data StreamId+data Duration+data TrackNumber+data ChosenByUser+data TaggingType+data RecentTracks+data UseRecs+data Scrobble+data Group+data Venue+data VenueName+data Multiplier = M1 | M2+data Bitrate = B64 | B128+data Name+data Station+data Discovery+data RTP+data BuyLinks+data LibraryAlbum+data LibraryArtist+++-- | Add artist parameter+artist :: Text -> Request f Artist+artist = add "artist"++-- | Add artists parameter+artists :: [Text] -> Request f [Artist]+artists = add "artists"++-- | Add album parameter+album :: Text -> Request f Album+album = add "album"++-- | Add MBID parameter+mbid :: Text -> Request f MBID+mbid = add "mbid"++-- | Add country parameter+country :: Text -> Request f Country+country = add "country"++-- | Add language parameter+language :: Text -> Request f Language+language = add "lang"++-- | Add tags parameter+tags :: [Text] -> Request f [Tag]+tags = add "tags"++-- | Add tag parameter+tag :: Text -> Request f Tag+tag = add "tag"++-- | Add autocorrect parameter+autocorrect :: Bool -> Request f Autocorrect+autocorrect = add "tags"++-- | Add page parameter+page :: Int64 -> Request f Page+page = add "page"++-- | Add limit parameter+limit :: Int64 -> Request f Limit+limit = add "limit"++-- | Add message parameter+message :: Text -> Request f Message+message = add "message"++-- | Add public parameter+public :: Bool -> Request f Public+public = add "public"++-- | Add recipient parameter+recipient :: Text -> Request f Recipient+recipient = add "recipient"++-- | Add username parameter+username :: Text -> Request f Username+username = add "username"++-- | Add user parameter+user :: Text -> Request f User+user = add "user"++-- | Add password parameter+password :: Text -> Request f Password+password = add "password"++-- | Add status parameter+status :: Status -> Request f Status+status = add "status" . T.pack . \s -> case s of+ Attending -> "0"+ Maybe -> "1"+ _ -> "2"++-- | Add event parameter+event :: Int64 -> Request f Event+event = add "event"++-- | Add festivalsonly parameter+festivalsonly :: Bool -> Request f Festivals+festivalsonly = add "festivalsonly"++-- | Add longitude parameter+longitude :: Text -> Request f Longitude+longitude = add "longitude"++-- | Add latitude parameter+latitude :: Text -> Request f Latitude+latitude = add "latitude"++-- | Add location parameter+location :: Text -> Request f Location+location = add "location"++-- | Add distance parameter+distance :: Int64 -> Request f Distance+distance = add "distance"++-- | Add venue parameter+venue :: Int64 -> Request f Venue+venue = add "venue"++-- | Add venue parameter+venueName :: Text -> Request f VenueName+venueName = add "venue"++-- | Add metro parameter+metro :: Text -> Request f Metro+metro = add "metro"++-- | Add start parameter+start :: Int64 -> Request f Start+start = add "start"++-- | Add end parameter+end :: Int64 -> Request f End+end = add "end"++-- | Add startTimestamp parameter+startTimestamp :: Int64 -> Request f StartTimestamp+startTimestamp = add "startTimestamp"++-- | Add endTimestamp parameter+endTimestamp :: Int64 -> Request f EndTimestamp+endTimestamp = add "endTimestamp"++-- | Add from parameter+from :: Int64 -> Request f From+from = add "from"++-- | Add to parameter+to :: Int64 -> Request f To+to = add "to"++-- | Add track parameter+track :: Text -> Request f Track+track = add "track"++-- | Add timestamp parameter+timestamp :: Int64 -> Request f Timestamp+timestamp = add "timestamp"++-- | Add playlistID parameter+playlist :: Int64 -> Request f Playlist+playlist = add "playlistID"++-- | Add title parameter+title :: Text -> Request f Title+title = add "title"++-- | Add description parameter+description :: Text -> Request f Description+description = add "description"++-- | Add fingerprint parameter+fingerprint :: Int64 -> Request f Fingerprint+fingerprint = add "fingerprintid"++-- | Add albumArtist parameter+albumArtist :: Text -> Request f AlbumArtist+albumArtist = add "albumArtist"++-- | Add context parameter+context :: Text -> Request f Context+context = add "context"++-- | Add streamId parameter+streamId :: Int64 -> Request f StreamId+streamId = add "streamId"++-- | Add duration parameter+duration :: Int64 -> Request f Duration+duration = add "duration"++-- | Add trackNumber parameter+trackNumber :: Int64 -> Request f TrackNumber+trackNumber = add "trackNumber"++-- | Add chosenByUser parameter+chosenByUser :: Bool -> Request f ChosenByUser+chosenByUser = add "chosenByUser"++-- | Add taggingType parameter+taggingType :: Text -> Request f TaggingType+taggingType = add "taggingtype"++-- | Add recentTracks parameter+recentTracks :: Bool -> Request f RecentTracks+recentTracks = add "recentTracks"++-- | Add useRecs parameter+useRecs :: Bool -> Request f UseRecs+useRecs = add "useRecs"++-- | Add group parameter+group :: Text -> Request f Group+group = add "group"++-- | Add multiplier parameter+multiplier :: Multiplier -> Request f Multiplier+multiplier m = case m of+ M1 -> add "speed_multiplier" (T.pack "1.0")+ M2 -> add "speed_multiplier" (T.pack "2.0")++-- | Add bitrate parameter+bitrate :: Bitrate -> Request f Bitrate+bitrate b = case b of+ B64 -> add "bitrate" (64 :: Int64)+ B128 -> add "bitrate" (128 :: Int64)++-- | Add name parameter+name :: Text -> Request f Name+name = add "name"++-- | Add station parameter+station :: Text -> Request f Station+station = add "station"++-- | Add group parameter+discovery :: Bool -> Request f Discovery+discovery = add "discovery"++-- | Add rtp parameter+rtp :: Bool -> Request f RTP+rtp = add "rtp"++-- | Add buyLinks parameter+buyLinks :: Bool -> Request f BuyLinks+buyLinks = add "buyLinks"+++class Targeted a where+ target :: Request f a -> Text++instance Targeted [Artist] where+ target _ = "artists"++instance Targeted User where+ target _ = "user"++-- | Add comparison parameter+comparison :: Targeted a => Int64 -> Request f a -> Request f a+comparison n t = let z = target t in+ add ("type" <> toText n) z <*> add ("value" <> toText n) (_query (unwrap t (R mempty mempty mempty)) M.! z)
+ src/Lastfm/Response.hs view
@@ -0,0 +1,248 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Request sending and Response parsing+module Lastfm.Response+ ( -- * Compute request signature+ -- $sign+ Secret(..)+ , sign+ -- * Perform requests+ , Connection+ , withConnection+ , newConnection+ , lastfm+ , lastfm_+ , Supported+ , Format(..)+ -- ** Errors+ , LastfmError(..)+ , _LastfmBadResponse+ , _LastfmEncodedError+ , _LastfmHttpError+#ifdef TEST+ , parse+ , md5+#endif+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif+import Control.Exception (SomeException(..), Exception(..), catch)+import Control.Monad.IO.Class (MonadIO(liftIO))+import Crypto.Hash (Digest, MD5, hash)+import Data.Aeson ((.:), Value(..), decode)+import Data.Aeson.Types (parseMaybe)+import qualified Data.ByteString.Lazy as Lazy+import qualified Data.ByteString as Strict+import Data.Map (Map)+import qualified Data.Map as M+import Data.Monoid+import Data.Profunctor (Choice, dimap, right')+import Data.String (IsString(..))+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Read as T+import Data.Typeable (Typeable, cast)+import qualified Network.HTTP.Client as Http+import qualified Network.HTTP.Client.TLS as Http+import Text.XML (Document, parseLBS, def)+import Text.XML.Cursor++import Lastfm.Internal+++-- $sign+--+-- The signature is required for every+-- authenticated API request. Basically,+-- every such request appends the md5 footprint+-- of its arguments to the query as+-- described at <http://www.last.fm/api/authspec#8>+++-- | 'Supported' provides parsing for the chosen 'Format'+--+-- 'JSON' is parsed to 'Value' type from aeson, while 'XML'+-- is parsed to 'Document' from xml-conduit+class Supported f r | f -> r, r -> f where+ prepareRequest :: R f -> R f+ parseResponseBody :: Lazy.ByteString -> Maybe r+ parseResponseEncodedError :: r -> Maybe LastfmError++instance Supported 'JSON Value where+ prepareRequest r = r { _query = M.singleton "format" "json" `M.union` _query r }+ parseResponseBody = decode+ parseResponseEncodedError = parseMaybe $ \(Object o) -> do+ code <- o .: "error"+ msg <- o .: "message"+ return (LastfmEncodedError code msg)++instance Supported 'XML Document where+ prepareRequest = id+ parseResponseBody = either (const Nothing) Just . parseLBS def+ parseResponseEncodedError doc = case fromDocument doc of+ cur+ | [mcode] <- cur $| element "lfm" >=> child >=> element "error" >=> attribute "code"+ , Right (code, _) <- T.decimal mcode+ , [msg] <- cur $| element "lfm" >=> child >=> element "error" >=> child >=> content+ -> Just (LastfmEncodedError code (T.strip msg))+ |+ otherwise -> Nothing++parse :: Supported f r => Lazy.ByteString -> Either LastfmError r+parse body = case parseResponseBody body of+ Just v+ | Just e <- parseResponseEncodedError v -> Left e+ | otherwise -> Right v+ Nothing -> Left (LastfmBadResponse body)++base :: R f+base = R+ { _host = "http://ws.audioscrobbler.com/2.0/"+ , _method = "GET"+ , _query = mempty+ }+++-- | Different ways last.fm response can be unusable+data LastfmError =+ -- | last.fm thinks it responded with something legible, but it really isn't+ LastfmBadResponse Lazy.ByteString+ -- | last.fm error code and message string+ | LastfmEncodedError Int Text+ -- | wrapped http-conduit exception+ | LastfmHttpError Http.HttpException+ deriving (Show, Typeable)++-- | Admittedly, this isn't the best 'Eq' instance ever+-- but not having 'Eq' 'C.HttpException' does not leave much a choice+instance Eq LastfmError where+ LastfmBadResponse bs == LastfmBadResponse bs' = bs == bs'+ LastfmEncodedError e s == LastfmEncodedError e' t = e == e' && s == t+ LastfmHttpError _ == LastfmHttpError _ = True+ _ == _ = False++instance Exception LastfmError where+ fromException e@(SomeException se)+ | Just e' <- fromException e = Just (LastfmHttpError e')+ | otherwise = cast se++class AsLastfmError t where+ _LastfmError :: (Choice p, Applicative m) => p LastfmError (m LastfmError) -> p t (m t)++instance AsLastfmError LastfmError where+ _LastfmError = id+ {-# INLINE _LastfmError #-}++instance AsLastfmError SomeException where+ _LastfmError = dimap (\e -> maybe (Left e) Right (fromException e)) (either pure (fmap toException)) . right'+ {-# INLINE _LastfmError #-}++-- | This is a @ Prism' 'LastfmError' 'Lazy.ByteString' @ in disguise+_LastfmBadResponse+ :: (Choice p, Applicative m, AsLastfmError e)+ => p Lazy.ByteString (m Lazy.ByteString) -> p e (m e)+_LastfmBadResponse = _LastfmError . dimap go (either pure (fmap LastfmBadResponse)) . right' where+ go (LastfmBadResponse bs) = Right bs+ go x = Left x+ {-# INLINE go #-}+{-# INLINE _LastfmBadResponse #-}++-- | This is a @ Prism' 'LastfmError' ('Int', 'String') @ in disguise+_LastfmEncodedError+ :: (Choice p, Applicative m, AsLastfmError e)+ => p (Int, Text) (m (Int, Text)) -> p e (m e)+_LastfmEncodedError = _LastfmError . dimap go (either pure (fmap (uncurry LastfmEncodedError))) . right' where+ go (LastfmEncodedError n v) = Right (n, v)+ go x = Left x+ {-# INLINE go #-}+{-# INLINE _LastfmEncodedError #-}++-- | This is a @ Prism' 'LastfmError' 'C.HttpException' @ in disguise+_LastfmHttpError+ :: (Choice p, Applicative m, AsLastfmError e)+ => p Http.HttpException (m Http.HttpException) -> p e (m e)+_LastfmHttpError = _LastfmError . dimap go (either pure (fmap LastfmHttpError)) . right' where+ go (LastfmHttpError e) = Right e+ go x = Left x+ {-# INLINE go #-}+{-# INLINE _LastfmHttpError #-}+++-- | Application secret+newtype Secret = Secret Text deriving (Show, Eq, Typeable)++instance IsString Secret where+ fromString = Secret . fromString++-- | Sign the 'Request' with the 'Secret' so it's ready to be sent+sign :: Secret -> Request f Sign -> Request f Ready+sign s = coerce . (<* signature)+ where+ signature = wrap $+ \r@R { _query = q } -> r { _query = apiSig s . authToken $ q }++authToken :: Map Text Text -> Map Text Text+authToken q = maybe q (M.delete "password") $ do+ password <- M.lookup "password" q+ username <- M.lookup "username" q+ return (M.insert "authToken" (md5 (username <> md5 password)) q)++apiSig :: Secret -> Map Text Text -> Map Text Text+apiSig (Secret s) q = M.insert "api_sig" (signer (foldr M.delete q ["format", "callback"])) q+ where+ signer = md5 . M.foldrWithKey (\k v xs -> k <> v <> xs) s++-- | Get supplied string md5 hash hex representation+md5 :: Text -> Text+md5 = T.pack . show . (hash :: Strict.ByteString -> Digest MD5) . T.encodeUtf8+++-- | Lastfm connection manager+newtype Connection = Connection Http.Manager++-- | Creating an HTTPS connection manager is expensive; it's advised to use+-- a single 'Connection' for all communications with last.fm+withConnection :: MonadIO m => (Connection -> m a) -> m a+withConnection f = f =<< newConnection++-- | Create a 'Connection'. Note that there's no need to close the connection manually,+-- as it will be closed automatically when no longer in use, that is, all the requests have+-- been processed and it's about to be garbage collected.+newConnection :: MonadIO m => m Connection+newConnection = liftIO (fmap Connection (Http.newManager Http.tlsManagerSettings))++-- | Perform the 'Request' and parse the response+lastfm :: Supported f r => Connection -> Request f Ready -> IO (Either LastfmError r)+lastfm man = lastfmWith man parse . finalize++-- | Perform the 'Request' ignoring any responses+lastfm_ :: Supported f r => Connection -> Request f Ready -> IO (Either LastfmError ())+lastfm_ man = lastfmWith man (\_ -> Right ()) . finalize++-- | Send the 'R' and parse the 'Response' with the supplied parser+lastfmWith+ :: Supported f r+ => Connection+ -> (Lazy.ByteString -> Either LastfmError a)+ -> R f+ -> IO (Either LastfmError a)+lastfmWith (Connection man) p r = do+ req <- Http.parseUrl (render r)+ let req' = req+ { Http.method = _method r+ , Http.responseTimeout = Just 10000000+ }+ p . Http.responseBody <$> Http.httpLbs req' man+ `catch`+ (return . Left)++-- | Get the 'R' from the 'Request'+finalize :: Supported f r => Request f Ready -> R f+finalize x = (prepareRequest . unwrap x) base
+ src/Lastfm/Tag.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm tag API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Tag as Tag+-- @+module Lastfm.Tag+ ( getInfo, getSimilar, getTopAlbums, getTopArtists, getTopTags, getTopTracks+ , getWeeklyArtistChart, getWeeklyChartList, search+ ) where++import Lastfm.Request+++-- | Get the metadata for a tag+--+-- Optional: language+--+-- <http://www.last.fm/api/show/tag.getInfo>+getInfo :: Request f (Tag -> APIKey -> Ready)+getInfo = api "tag.getInfo"+++-- | Search for tags similar to this one. Returns tags ranked by similarity, based on listening data.+--+-- <http://www.last.fm/api/show/tag.getSimilar>+getSimilar :: Request f (Tag -> APIKey -> Ready)+getSimilar = api "tag.getSimilar"+++-- | Get the top albums tagged by this tag, ordered by tag count.+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/tag.getTopAlbums>+getTopAlbums :: Request f (Tag -> APIKey -> Ready)+getTopAlbums = api "tag.getTopAlbums"+++-- | Get the top artists tagged by this tag, ordered by tag count.+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/tag.getTopArtists>+getTopArtists :: Request f (Tag -> APIKey -> Ready)+getTopArtists = api "tag.getTopArtists"+++-- | Fetches the top global tags on Last.fm, sorted by popularity (number of times used)+--+-- <http://www.last.fm/api/show/tag.getTopTags>+getTopTags :: Request f (APIKey -> Ready)+getTopTags = api "tag.getTopTags"+++-- | Get the top tracks tagged by this tag, ordered by tag count.+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/tag.getTopTracks>+getTopTracks :: Request f (Tag -> APIKey -> Ready)+getTopTracks = api "tag.getTopTracks"+++-- | Get an artist chart for a tag, for a given date range.+-- If no date range is supplied, it will return the most recent artist chart for this tag.+--+-- Optional: 'from', 'to', 'limit'+--+-- <http://www.last.fm/api/show/tag.getWeeklyArtistChart>+getWeeklyArtistChart :: Request f (Tag -> APIKey -> Ready)+getWeeklyArtistChart = api "tag.getWeeklyArtistChart"+++-- | Get a list of available charts for this tag, expressed as+-- date ranges which can be sent to the chart services.+--+-- <http://www.last.fm/api/show/tag.getWeeklyChartList>+getWeeklyChartList :: Request f (Tag -> APIKey -> Ready)+getWeeklyChartList = api "tag.getWeeklyChartList"+++-- | Search for a tag by name. Returns matches sorted by relevance.+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/tag.search>+search :: Request f (Tag -> APIKey -> Ready)+search = api "tag.search"
+ src/Lastfm/Tasteometer.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm tasteometer API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Tasteometer as Tasteometer+-- @+module Lastfm.Tasteometer where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif++import Lastfm.Request+++-- | Get a Tasteometer score from two inputs, along with a list of shared artists.+-- If the input is a user some additional information is returned.+--+-- Optional: 'limit'+--+-- <http://www.last.fm/api/show/tasteometer.compare>+compare :: (Targeted u, Targeted v) => Request f u -> Request f v -> Request f (APIKey -> Ready)+compare u v = api "tasteometer.compare" <* comparison 1 u <* comparison 2 v
+ src/Lastfm/Track.hs view
@@ -0,0 +1,203 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm track API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Track as Track+-- @+module Lastfm.Track+ ( ArtistTrackOrMBID+ , addTags, ban, getBuyLinks, getCorrection, getFingerprintMetadata+ , getInfo, getShouts, getSimilar, getTags, getTopFans+ , getTopTags, love, removeTag, scrobble, item+ , search, share, unban, unlove, updateNowPlaying+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as N++import Lastfm.Internal (absorbQuery, indexedWith, wrap)+import Lastfm.Request+++-- | Unify ('Artist' -> 'Track' -> …) and ('MBID' -> …)+class ArtistTrackOrMBID a++instance ArtistTrackOrMBID (MBID -> APIKey -> Ready)+instance ArtistTrackOrMBID (Artist -> Track -> APIKey -> Ready)+++-- | Tag a track using a list of user supplied tags.+--+-- <http://www.last.fm/api/show/track.addTags>+addTags :: Request f (Artist -> Track -> [Tag] -> APIKey -> SessionKey -> Sign)+addTags = api "track.addTags" <* post+++-- | Ban a track for a given user profile.+--+-- <http://www.last.fm/api/show/track.ban>+ban :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)+ban = api "track.ban" <* post+++-- | Get a list of Buy Links for a particular track.+--+-- Optional: 'autocorrect'+--+-- <http://www.last.fm/api/show/track.getBuylinks>+getBuyLinks :: ArtistTrackOrMBID t => Request f (Country -> t)+getBuyLinks = api "track.getBuyLinks"+++-- | Use the last.fm corrections data to check whether+-- the supplied track has a correction to a canonical track.+--+-- <http://www.last.fm/api/show/track.getCorrection>+getCorrection :: Request f (Artist -> Track -> APIKey -> Ready)+getCorrection = api "track.getCorrection"+++-- | Retrieve track metadata associated with a fingerprint id+-- generated by the Last.fm Fingerprinter. Returns track+-- elements, along with a 'rank' value between 0 and 1 reflecting the confidence for each match.+--+-- <http://www.last.fm/api/show/track.getFingerprintMetadata>+getFingerprintMetadata :: Request f (Fingerprint -> APIKey -> Ready)+getFingerprintMetadata = api "track.getFingerprintMetadata"+++-- | Get the metadata for a track on Last.fm.+--+-- Optional: 'autocorrect', 'username'+--+-- <http://www.last.fm/api/show/track.getInfo>+getInfo :: ArtistTrackOrMBID t => Request f t+getInfo = api "track.getInfo"+++-- | Get shouts for this track. Also available as an rss feed.+--+-- Optional: 'autocorrect', 'limit', 'page'+--+-- <http://www.last.fm/api/show/track.getShouts>+getShouts :: ArtistTrackOrMBID t => Request f t+getShouts = api "track.getShouts"+++-- | Get the similar tracks for this track on Last.fm, based on listening data.+--+-- Optional: 'autocorrect', 'limit'+--+-- <http://www.last.fm/api/show/track.getSimilar>+getSimilar :: ArtistTrackOrMBID t => Request f t+getSimilar = api "track.getSimilar"+++-- | Get the tags applied by an individual user to a track on Last.fm.+--+-- Optional: 'autocorrect', 'user'+--+-- <http://www.last.fm/api/show/track.getTags>+getTags :: ArtistTrackOrMBID t => Request f t+getTags = api "track.getTags"+++-- | Get the top fans for this track on Last.fm, based on listening data.+--+-- Optional: 'autocorrect'+--+-- <http://www.last.fm/api/show/track.getTopFans>+getTopFans :: ArtistTrackOrMBID t => Request f t+getTopFans = api "track.getTopFans"+++-- | Get the top tags for this track on Last.fm, ordered by tag count.+--+-- Optional: 'autocorrect'+--+-- <http://www.last.fm/api/show/track.getTopTags>+getTopTags :: ArtistTrackOrMBID t => Request f t+getTopTags = api "track.getTopTags"+++-- | Love a track for a user profile.+--+-- <http://www.last.fm/api/show/track.love>+love :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)+love = api "track.love" <* post+++-- | Remove a user's tag from a track.+--+-- <http://www.last.fm/api/show/track.removeTag>+removeTag :: Request f (Artist -> Track -> Tag -> APIKey -> SessionKey -> Sign)+removeTag = api "track.removeTag" <* post+++-- | Add played tracks to the user profile.+--+-- Scrobbles 50 first list elements+--+-- <http://www.last.fm/api/show/track.scrobble>+scrobble :: NonEmpty (Request f Scrobble) -> Request f (APIKey -> SessionKey -> Sign)+scrobble batch = api "track.scrobble" <* items <* post+ where+ items = absorbQuery (N.zipWith indexedWith (N.fromList [0..49]) batch)++-- | What track to scrobble?+--+-- Optional: 'album', 'albumArtist', 'chosenByUser', 'context',+-- 'duration', 'mbid', 'streamId', 'trackNumber'+item :: Request f (Artist -> Track -> Timestamp -> Scrobble)+item = wrap id+++-- | Search for a track by track name. Returns track matches sorted by relevance.+--+-- Optional: 'artist', 'limit', 'page'+--+-- <http://www.last.fm/api/show/track.search>+search :: Request f (Track -> APIKey -> Ready)+search = api "track.search"+++-- | Share a track twith one or more Last.fm users or other friends.+--+-- Optional: 'public', 'message', 'recipient'+--+-- <http://www.last.fm/api/show/track.share>+share :: Request f (Artist -> Track -> Recipient -> APIKey -> SessionKey -> Sign)+share = api "track.share" <* post+++-- | Unban a track for a user profile.+--+-- <http://www.last.fm/api/show/track.unban>+unban :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)+unban = api "track.unban" <* post+++-- | Unlove a track for a user profile.+--+-- <http://www.last.fm/api/show/track.unlove>+unlove :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)+unlove = api "track.unlove" <* post+++-- | Used to notify Last.fm that a user has started listening+-- to a track. Parameter names are case sensitive.+--+-- Optional: 'album', 'albumArtist', 'context',+-- 'duration', 'mbid', 'trackNumber'+--+-- <http://www.last.fm/api/show/track.updateNowPlaying>+updateNowPlaying :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)+updateNowPlaying = api "track.updateNowPlaying" <* post
+ src/Lastfm/User.hs view
@@ -0,0 +1,253 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm user API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.User as User+-- @+module Lastfm.User+ ( getArtistTracks, getBannedTracks, getEvents, getFriends+ , getInfo, getLovedTracks, getNeighbours, getNewReleases+ , getPastEvents, getPersonalTags, getPlaylists, getRecentStations+ , getRecentTracks, getRecommendedArtists, getRecommendedEvents+ , getShouts, getTopAlbums, getTopArtists, getTopTags+ , getTopTracks, getWeeklyAlbumChart, getWeeklyArtistChart+ , getWeeklyChartList, getWeeklyTrackChart, shout+ ) where++#if __GLASGOW_HASKELL__ < 710+import Control.Applicative+#endif++import Lastfm.Request+++-- | Get a list of tracks by a given artist scrobbled by this user+-- , including scrobble time. Can be limited to specific timeranges, defaults to all time.+--+-- Optional: 'startTimestamp', 'page', 'endTimestamp'+--+-- <http://www.last.fm/api/show/user.getArtistTracks>+getArtistTracks :: Request f (User -> Artist -> APIKey -> Ready)+getArtistTracks = api "user.getArtistTracks"+++-- | Returns the tracks banned by the user+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/user.getBannedTracks>+getBannedTracks :: Request f (User -> APIKey -> Ready)+getBannedTracks = api "user.getBannedTracks"+++-- | Get a list of upcoming events that this user is attending.+-- Easily integratable into calendars, using the ical standard (see 'more formats' section below).+--+-- Optional: 'page', 'festivalsonly', 'limit'+--+-- <http://www.last.fm/api/show/user.getEvents>+getEvents :: Request f (User -> APIKey -> Ready)+getEvents = api "user.getEvents"+++-- | Get a list of the user's friends on Last.fm.+--+-- Optional: 'recenttracks', 'limit', 'page'+--+-- <http://www.last.fm/api/show/user.getFriends>+getFriends :: Request f (User -> APIKey -> Ready)+getFriends = api "user.getFriends"+++-- | Get information about a user profile.+--+-- <http://www.last.fm/api/show/user.getInfo>+getInfo :: Request f (User -> APIKey -> Ready)+getInfo = api "user.getInfo"+++-- | Get the last 50 tracks loved by a user.+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/user.getLovedTracks>+getLovedTracks :: Request f (User -> APIKey -> Ready)+getLovedTracks = api "user.getLovedTracks"+++-- | Get a list of a user's neighbours on Last.fm.+--+-- Optional: 'limit'+--+-- <http://www.last.fm/api/show/user.getNeighbours>+getNeighbours :: Request f (User -> APIKey -> Ready)+getNeighbours = api "user.getNeighbours"+++-- | Gets a list of forthcoming releases based on a user's musical taste.+--+-- Optional: 'userecs'+--+-- <http://www.last.fm/api/show/user.getNewReleases>+getNewReleases :: Request f (User -> APIKey -> Ready)+getNewReleases = api "user.getNewReleases"+++-- | Get a paginated list of all events a user has attended in the past.+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/user.getPastEvents>+getPastEvents :: Request f (User -> APIKey -> Ready)+getPastEvents = api "user.getPastEvents"+++-- | Get the user's personal tags+--+-- Optional: 'taggingtype', 'limit', 'page'+--+-- <http://www.last.fm/api/show/user.getPersonalTags>+getPersonalTags :: Request f (User -> Tag -> TaggingType -> APIKey -> Ready)+getPersonalTags = api "user.getPersonalTags"+++-- | Get a list of a user's playlists on Last.fm.+--+-- <http://www.last.fm/api/show/user.getPlaylists>+getPlaylists :: Request f (User -> APIKey -> Ready)+getPlaylists = api "user.getPlaylists"+++-- | Get a list of the recent Stations listened to by this user.+--+-- Optional: 'limit', 'page'+--+-- <http://www.last.fm/api/show/user.getRecentStations>+getRecentStations :: Request f (User -> APIKey -> SessionKey -> Sign)+getRecentStations = api "user.getRecentStations"+++-- | Get a list of the recent tracks listened to by this user.+-- Also includes the currently playing track with the nowplaying="true"+-- attribute if the user is currently listening.+--+-- Optional: 'limit', 'page', 'from', 'extended', 'to'+--+-- <http://www.last.fm/api/show/user.getRecentTracks>+getRecentTracks :: Request f (User -> APIKey -> Ready)+getRecentTracks = api "user.getRecentTracks"+++-- | Get Last.fm artist recommendations for a user+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/user.getRecommendedArtists>+getRecommendedArtists :: Request f (APIKey -> SessionKey -> Sign)+getRecommendedArtists = api "user.getRecommendedArtists"+++-- | Get a paginated list of all events recommended to a user by Last.fm, based on their listening profile.+--+-- Optional: 'limit', 'page', 'latitude', 'longitude', 'festivalsonly', 'country'+--+-- <http://www.last.fm/api/show/user.getRecommendedEvents>+getRecommendedEvents :: Request f (APIKey -> SessionKey -> Sign)+getRecommendedEvents = api "user.getRecommendedEvents"+++-- | Get shouts for this user. Also available as an rss feed.+--+-- Optional: 'page', 'limit'+--+-- <http://www.last.fm/api/show/user.getShouts>+getShouts :: Request f (User -> APIKey -> Ready)+getShouts = api "user.getShouts"+++-- | Get the top albums listened to by a user.+-- You can stipulate a time period. Sends the overall chart by default.+--+-- Optional: 'period', 'limit', 'page'+--+-- <http://www.last.fm/api/show/user.getTopAlbums>+getTopAlbums :: Request f (User -> APIKey -> Ready)+getTopAlbums = api "user.getTopAlbums"+++-- | Get the top artists listened to by a user.+-- You can stipulate a time period. Sends the overall chart by default.+--+-- Optional: 'period', 'limit', 'page'+--+-- <http://www.last.fm/api/show/user.getTopArtists>+getTopArtists :: Request f (User -> APIKey -> Ready)+getTopArtists = api "user.getTopArtists"+++-- | Get the top tags used by this user.+--+-- Optional: 'limit'+--+-- <http://www.last.fm/api/show/user.getTopTags>+getTopTags :: Request f (User -> APIKey -> Ready)+getTopTags = api "user.getTopTags"+++-- | Get the top tracks listened to by a user.+-- You can stipulate a time period. Sends the overall chart by default.+--+-- Optional: 'period', 'limit', 'page'+--+-- <http://www.last.fm/api/show/user.getTopTracks>+getTopTracks :: Request f (User -> APIKey -> Ready)+getTopTracks = api "user.getTopTracks"+++-- | Get an album chart for a user profile, for a given date range.+-- If no date range is supplied, it will return the most recent album chart for this user.+--+-- Optional: 'from', 'to'+--+-- <http://www.last.fm/api/show/user.getWeeklyAlbumChart>+getWeeklyAlbumChart :: Request f (User -> APIKey -> Ready)+getWeeklyAlbumChart = api "user.getWeeklyAlbumChart"+++-- | Get an artist chart for a user profile, for a given date range.+-- If no date range is supplied, it will return the most recent artist chart for this user.+--+-- Optional: 'from', 'to'+--+-- <http://www.last.fm/api/show/user.getWeeklyArtistChart>+getWeeklyArtistChart :: Request f (User -> APIKey -> Ready)+getWeeklyArtistChart = api "user.getWeeklyArtistChart"+++-- | Get a list of available charts for this user, expressed as+-- date ranges which can be sent to the chart services.+--+-- <http://www.last.fm/api/show/user.getWeeklyChartList>+getWeeklyChartList :: Request f (User -> APIKey -> Ready)+getWeeklyChartList = api "user.getWeeklyChartList"+++-- | Get a track chart for a user profile, for a given date range.+-- If no date range is supplied, it will return the most recent track chart for this user.+--+-- Optional: 'from', 'to'+--+-- <http://www.last.fm/api/show/user.getWeeklyTrackChart>+getWeeklyTrackChart :: Request f (User -> APIKey -> Ready)+getWeeklyTrackChart = api "user.getWeeklyTrackChart"+++-- | Shout on this user's shoutbox+--+-- <http://www.last.fm/api/show/user.shout>+shout :: Request f (User -> Message -> APIKey -> SessionKey -> Sign)+shout = api "user.shout" <* post
+ src/Lastfm/Venue.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+-- | Lastfm venue API+--+-- This module is intended to be imported qualified:+--+-- @+-- import qualified Lastfm.Venue as Venue+-- @+module Lastfm.Venue+ ( getEvents, getPastEvents, search+ ) where++import Lastfm.Request+++-- | Get a list of upcoming events at this venue.+--+-- Optional: 'festivalsonly'+--+-- <http://www.last.fm/api/show/venue.getEvents>+getEvents :: Request f (Venue -> APIKey -> Ready)+getEvents = api "venue.getEvents"+++-- | Get a paginated list of all the events held at this venue in the past.+--+-- Optional: 'festivalsonly', 'page', 'limit'+--+-- <http://www.last.fm/api/show/venue.getPastEvents>+getPastEvents :: Request f (Venue -> APIKey -> Ready)+getPastEvents = api "venue.getPastEvents"+++-- | Search for a venue by venue name+--+-- Optional: 'page', 'limit', 'country'+--+-- <http://www.last.fm/api/show/venue.search>+search :: Request f (VenueName -> APIKey -> Ready)+search = api "venue.search"
− src/Network/Lastfm.hs
@@ -1,14 +0,0 @@--- | Lastfm API interface-module Network.Lastfm- ( -- * Utilities for constructing requests- module Network.Lastfm.Request- -- * Utilities for signing and sending requests- , module Network.Lastfm.Response- -- * Control.Applicative is re-exported for convenience- , module Control.Applicative- ) where--import Control.Applicative--import Network.Lastfm.Request-import Network.Lastfm.Response
− src/Network/Lastfm/Album.hs
@@ -1,117 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm album API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Album as Album--- @-module Network.Lastfm.Album- ( ArtistAlbumOrMBID- , addTags, getBuyLinks, getInfo, getShouts- , getTags, getTopTags, removeTag, search, share- ) where--import Control.Applicative--import Network.Lastfm.Request----- | Unify ('Artist' -> 'Album' -> …) and ('MBID' -> …)-class ArtistAlbumOrMBID r a | a -> r--instance ArtistAlbumOrMBID r (MBID -> APIKey -> r)-instance ArtistAlbumOrMBID r (Artist -> Album -> APIKey -> r)----- | Tag an album using a list of user supplied tags.------ <http://www.last.fm/api/show/album.addTags>-addTags :: Request f (Artist -> Album -> [Tag] -> APIKey -> SessionKey -> Sign)-addTags = api "album.addTags" <* post-{-# INLINE addTags #-}----- | Get a list of Buy Links for a particular Album. It is--- required that you supply either the artist and track params or the mbid parameter.------ Optional: 'autocorrect'------ <http://www.last.fm/api/show/album.getBuylinks>-getBuyLinks :: ArtistAlbumOrMBID Ready a => Request f (Country -> a)-getBuyLinks = api "album.getBuyLinks"-{-# INLINE getBuyLinks #-}----- | Get the metadata for an album on Last.fm using the album name or--- a musicbrainz id. See playlist.fetch on how to get the album playlist.------ Optional: 'autocorrect', 'username', 'language'------ <http://www.last.fm/api/show/album.getInfo>-getInfo :: ArtistAlbumOrMBID Ready a => Request f a-getInfo = api "album.getInfo"-{-# INLINE getInfo #-}----- | Get shouts for this album.------ Optional: 'autocorrect', 'limit', 'page'------ <http://www.last.fm/api/show/album.getShouts>-getShouts :: ArtistAlbumOrMBID Ready a => Request f a-getShouts = api "album.getShouts"-{-# INLINE getShouts #-}----- | Get the tags applied by an individual user to an album on Last.fm.------ Optional: 'autocorrect', 'user'------ <http://www.last.fm/api/show/album.getTags>-getTags :: ArtistAlbumOrMBID r a => Request f a-getTags = api "album.getTags"-{-# INLINE getTags #-}----- | Get the top tags for an album on Last.fm, ordered by popularity.------ Optional: 'autocorrect'------ <http://www.last.fm/api/show/album.getTopTags>-getTopTags :: ArtistAlbumOrMBID Ready a => Request f a-getTopTags = api "album.getTopTags"-{-# INLINE getTopTags #-}----- | Remove a user's tag from an album.------ <http://www.last.fm/api/show/album.removeTag>-removeTag :: Request f (Artist -> Album -> Tag -> APIKey -> SessionKey -> Sign)-removeTag = api "album.removeTag" <* post-{-# INLINE removeTag #-}----- | Search for an album by name. Returns album matches sorted by relevance.------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/album.search>-search :: Request f (Album -> APIKey -> Ready)-search = api "album.search"-{-# INLINE search #-}----- | Share an album with one or more Last.fm users or other friends.------ Optional: 'public', 'message', 'recipient'------ <http://www.last.fm/api/show/album.share>-share :: Request f (Album -> Artist -> Recipient -> APIKey -> SessionKey -> Sign)-share = api "album.share" <* post-{-# INLINE share #-}
− src/Network/Lastfm/Artist.hs
@@ -1,198 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm artist API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Artist as Artist--- @-module Network.Lastfm.Artist- ( ArtistOrMBID- , addTags, getCorrection, getEvents, getInfo- , getPastEvents, getPodcast, getShouts- , getSimilar, getTags, getTopAlbums- , getTopFans, getTopTags, getTopTracks- , removeTag, search, share, shout- ) where--import Control.Applicative--import Network.Lastfm.Request---- | Unify ('Artist' -> …) and ('MBID' -> …)-class ArtistOrMBID r a--instance ArtistOrMBID r MBID-instance ArtistOrMBID r Artist----- | Tag an artist with one or more user supplied tags.------ <http://www.last.fm/api/show/artist.addTags>-addTags :: Request f (Artist -> [Tag] -> APIKey -> SessionKey -> Sign)-addTags = api "artist.addTags" <* post-{-# INLINE addTags #-}----- | Use the last.fm corrections data to check whether the--- supplied artist has a correction to a canonical artist------ <http://www.last.fm/api/show/artist.getCorrection>-getCorrection :: Request f (Artist -> APIKey -> Ready)-getCorrection = api "artist.getCorrection"-{-# INLINE getCorrection #-}----- | Get a list of upcoming events for this artist. Easily--- integratable into calendars, using the ical standard (see feeds section below).------ Optional: 'autocorrect', 'limit', 'pages', 'festivalsonly'------ <http://www.last.fm/api/show/artist.getEvents>-getEvents :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)-getEvents = api "artist.getEvents"-{-# INLINE getEvents #-}----- | Get the metadata for an artist. Includes biography.------ Optional: 'language', 'autocorrect', 'username'------ <http://www.last.fm/api/show/artist.getInfo>-getInfo :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)-getInfo = api "artist.getInfo"-{-# INLINE getInfo #-}----- | Get a paginated list of all the events this artist has played at in the past.------ Optional: 'page', 'autocorrect', 'limit'------ <http://www.last.fm/api/show/artist.getPastEvents>-getPastEvents :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)-getPastEvents = api "artist.getPastEvents"-{-# INLINE getPastEvents #-}----- | Get a podcast of free mp3s based on an artist------ Optional: 'autocorrect'------ <http://www.last.fm/api/show/artist.getPodcast>-getPodcast :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)-getPodcast = api "artist.getPodcast"-{-# INLINE getPodcast #-}----- | Get shouts for this artist. Also available as an rss feed.------ Optional:'autocorrect', 'limit', 'page'------ <http://www.last.fm/api/show/artist.getShouts>-getShouts :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)-getShouts = api "artist.getShouts"-{-# INLINE getShouts #-}----- | Get all the artists similar to this artist------ Optional: 'limit', 'autocorrect'------ <http://www.last.fm/api/show/artist.getSimilar>-getSimilar :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)-getSimilar = api "artist.getSimilar"-{-# INLINE getSimilar #-}----- | Get the tags applied by an individual user to an artist on Last.fm.--- If accessed as an authenticated service /and/ you don't supply a--- user parameter then this service will return tags for--- the authenticated user. To retrieve the list of top tags applied--- to an artist by all users use 'Network.Lastfm.Artist.getTopTags'.------ Optional: 'user', 'autocorrect'------ <http://www.last.fm/api/show/artist.getTags>-getTags :: ArtistOrMBID r a => Request f (a -> APIKey -> r)-getTags = api "artist.getTags"-{-# INLINE getTags #-}----- | Get the top albums for an artist on Last.fm, ordered by popularity.------ Optional: 'autocorrect', 'page', 'limit'------ <http://www.last.fm/api/show/artist.getTopAlbums>-getTopAlbums :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)-getTopAlbums = api "artist.getTopAlbums"-{-# INLINE getTopAlbums #-}----- | Get the top fans for an artist on Last.fm, based on listening data.------ Optional: 'autocorrect'------ <http://www.last.fm/api/show/artist.getTopFans>-getTopFans :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)-getTopFans = api "artist.getTopFans"-{-# INLINE getTopFans #-}----- | Get the top tags for an artist on Last.fm, ordered by popularity.------ Optional: 'autocorrect'------ <http://www.last.fm/api/show/artist.getTopTags>-getTopTags :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)-getTopTags = api "artist.getTopTags"-{-# INLINE getTopTags #-}----- | Get the top tracks by an artist on Last.fm, ordered by popularity------ Optional: 'autocorrect', 'page', 'limit'------ <http://www.last.fm/api/show/artist.getTopTracks>-getTopTracks :: ArtistOrMBID Ready a => Request f (a -> APIKey -> Ready)-getTopTracks = api "artist.getTopTracks"-{-# INLINE getTopTracks #-}----- | Remove a user's tag from an artist.------ <http://www.last.fm/api/show/artist.removeTag>-removeTag :: Request f (Artist -> Tag -> APIKey -> SessionKey -> Sign)-removeTag = api "artist.removeTag" <* post-{-# INLINE removeTag #-}----- | Search for an artist by name. Returns artist matches sorted by relevance.------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/artist.search>-search :: Request f (Artist -> APIKey -> Ready)-search = api "artist.search"-{-# INLINE search #-}----- | Share an artist with Last.fm users or other friends.------ Optional: 'message', 'public'------ <http://www.last.fm/api/show/artist.share>-share :: Request f (Artist -> Recipient -> APIKey -> SessionKey -> Sign)-share = api "artist.share" <* post-{-# INLINE share #-}----- | Shout in this artist's shoutbox------ <http://www.last.fm/api/show/artist.shout>-shout :: Request f (Artist -> Message -> APIKey -> SessionKey -> Sign)-shout = api "artist.shout" <* post-{-# INLINE shout #-}
− src/Network/Lastfm/Authentication.hs
@@ -1,67 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm authentication procedure helpers------ Basically, lastfm provides 3 ways to authenticate user:------ - web application - <http://www.last.fm/api/webauth>------ - desktop application - <http://www.last.fm/api/desktopauth>------ - modile application - <http://www.last.fm/api/mobileauth>------ Note that you can use any of them in your--- application despite their names------ How to get session key for yourself for debug with GHCi:------ >>> import Network.Lastfm--- >>> import Network.Lastfm.Authentication--- >>> :set -XOverloadedStrings--- >>> con <- newConnection--- >>> lastfm con $ getToken <*> apiKey "__API_KEY__" <* json--- Right (Object (fromList [("token",String "__TOKEN__")]))--- >>> putStrLn . link $ apiKey "__API_KEY__" <* token "__TOKEN__"--- http://www.last.fm/api/auth/?api_key=__API_KEY__&token=__TOKEN__--- >>> -- Click that link ^^^--- >>> lastfm con $ sign "__SECRET__" $ getSession <*> token "__TOKEN__" <*> apiKey "__API_KEY__" <* json--- Right (Object (fromList [("session",Object (fromList [("subscriber",String "0"),("key",String "__SESSION_KEY__"),("name",String "__USER__")]))]))-module Network.Lastfm.Authentication- ( -- * Helpers- getToken, getSession, getMobileSession- , link- ) where--import Control.Applicative ((<*))-import Data.Monoid--import Network.Lastfm.Internal-import Network.Lastfm.Request----- | Get authorization token-getToken :: Request f (APIKey -> Ready)-getToken = api "auth.getToken"-{-# INLINE getToken #-}----- | Get session key-getMobileSession :: Request f (Username -> Password -> APIKey -> Sign)-getMobileSession = api "auth.getMobileSession" <* post-{-# INLINE getMobileSession #-}----- | Get session key-getSession :: Request f (Token -> APIKey -> Sign)-getSession = api "auth.getSession"-{-# INLINE getSession #-}----- | Construct link user should follow to approve application-link :: Request f a -> String-link q = render . unwrap q $ R- { _host = "http://www.last.fm/api/auth/"- , _method = mempty- , _query = mempty- }-{-# INLINE link #-}
− src/Network/Lastfm/Chart.hs
@@ -1,75 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm chart API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Chart as Chart--- @-module Network.Lastfm.Chart- ( getHypedArtists, getHypedTracks, getLovedTracks- , getTopArtists, getTopTags, getTopTracks- ) where--import Network.Lastfm.Request----- | Get the hyped artists chart------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/chart.getHypedArtists>-getHypedArtists :: Request f (APIKey -> Ready)-getHypedArtists = api "chart.getHypedArtists"-{-# INLINE getHypedArtists #-}----- | Get the top artists chart------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/chart.getHypedTracks>-getHypedTracks :: Request f (APIKey -> Ready)-getHypedTracks = api "chart.getHypedTracks"-{-# INLINE getHypedTracks #-}----- | Get the most loved tracks chart------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/chart.getLovedTracks>-getLovedTracks :: Request f (APIKey -> Ready)-getLovedTracks = api "chart.getLovedTracks"-{-# INLINE getLovedTracks #-}----- | Get the top artists chart------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/chart.getTopArtists>-getTopArtists :: Request f (APIKey -> Ready)-getTopArtists = api "chart.getTopArtists"-{-# INLINE getTopArtists #-}----- | Get the top artists chart------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/chart.getTopTags>-getTopTags :: Request f (APIKey -> Ready)-getTopTags = api "chart.getTopTags"-{-# INLINE getTopTags #-}----- | Get the top tracks chart------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/chart.getTopTracks>-getTopTracks :: Request f (APIKey -> Ready)-getTopTracks = api "chart.getTopTracks"-{-# INLINE getTopTracks #-}
− src/Network/Lastfm/Event.hs
@@ -1,70 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm event API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Event as Event--- @-module Network.Lastfm.Event- ( attend, getAttendees, getInfo, getShouts, share, shout- ) where--import Control.Applicative--import Network.Lastfm.Request----- | Set a user's attendance status for an event.------ <http://www.last.fm/api/show/event.attend>-attend :: Request f (Event -> Status -> APIKey -> SessionKey -> Sign)-attend = api "event.attend" <* post-{-# INLINE attend #-}----- | Get a list of attendees for an event.------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/event.getAttendees>-getAttendees :: Request f (Event -> APIKey -> Ready)-getAttendees = api "event.getAttendees"-{-# INLINE getAttendees #-}----- | Get the metadata for an event on Last.fm. Includes attendance and lineup information.------ <http://www.last.fm/api/show/event.getInfo>-getInfo :: Request f (Event -> APIKey -> Ready)-getInfo = api "event.getInfo"-{-# INLINE getInfo #-}----- | Get shouts for this event. Also available as an rss feed.------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/event.getShouts>-getShouts :: Request f (Event -> APIKey -> Ready)-getShouts = api "event.getShouts"-{-# INLINE getShouts #-}----- | Share an event with one or more Last.fm users or other friends.------ Optional: 'public', 'message'------ <http://www.last.fm/api/show/event.share>-share :: Request f (Event -> Recipient -> APIKey -> SessionKey -> Sign)-share = api "event.share" <* post-{-# INLINE share #-}----- | Shout in this event's shoutbox------ <http://www.last.fm/api/show/event.shout>-shout :: Request f (Event -> Message -> APIKey -> SessionKey -> Sign)-shout = api "event.shout" <* post-{-# INLINE shout #-}
− src/Network/Lastfm/Geo.hs
@@ -1,126 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm geo API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Geo as Geo--- @-module Network.Lastfm.Geo- ( getEvents, getMetroArtistChart, getMetroHypeArtistChart- , getMetroHypeTrackChart, getMetroTrackChart, getMetroUniqueArtistChart- , getMetroUniqueTrackChart, getMetroWeeklyChartlist, getMetros- , getTopArtists, getTopTracks- ) where--import Network.Lastfm.Request----- | Get all events in a specific location by country or city name.------ Optional: 'longitude', 'latitude', 'location', 'distance', 'page', 'tag', 'festivalsonly', 'limit'------ <http://www.last.fm/api/show/geo.getEvents>-getEvents :: Request f (APIKey -> Ready)-getEvents = api "geo.getEvents"-{-# INLINE getEvents #-}----- | Get a chart of artists for a metro------ Optional: 'start', 'end', 'page', 'limit'------ <http://www.last.fm/api/show/geo.getMetroArtistChart>-getMetroArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)-getMetroArtistChart = api "geo.getMetroArtistChart"-{-# INLINE getMetroArtistChart #-}----- | Get a chart of hyped (up and coming) artists for a metro------ Optional: 'start', 'end', 'page', 'limit'------ <http://www.last.fm/api/show/geo.getMetroHypeArtistChart>-getMetroHypeArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)-getMetroHypeArtistChart = api "geo.getMetroHypeArtistChart"-{-# INLINE getMetroHypeArtistChart #-}----- | Get a chart of tracks for a metro------ Optional: 'start', 'end', 'page', 'limit'------ <http://www.last.fm/api/show/geo.getMetroHypeTrackChart>-getMetroHypeTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)-getMetroHypeTrackChart = api "geo.getMetroHypeTrackChart"-{-# INLINE getMetroHypeTrackChart #-}----- | Get a chart of tracks for a metro------ Optional: 'start', 'end', 'page', 'limit'------ <http://www.last.fm/api/show/geo.getMetroTrackChart>-getMetroTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)-getMetroTrackChart = api "geo.getMetroTrackChart"-{-# INLINE getMetroTrackChart #-}----- | Get a chart of the artists which make that metro unique------ Optional: 'start', 'end', 'page', 'limit'------ <http://www.last.fm/api/show/geo.getMetroUniqueArtistChart>-getMetroUniqueArtistChart :: Request f (Metro -> Country -> APIKey -> Ready)-getMetroUniqueArtistChart = api "geo.getMetroUniqueArtistChart"-{-# INLINE getMetroUniqueArtistChart #-}----- | Get a chart of tracks for a metro------ Optional: 'start', 'end', 'page', 'limit'------ <http://www.last.fm/api/show/geo.getMetroUniqueTrackChart>-getMetroUniqueTrackChart :: Request f (Metro -> Country -> APIKey -> Ready)-getMetroUniqueTrackChart = api "geo.getMetroUniqueTrackChart"-{-# INLINE getMetroUniqueTrackChart #-}----- | Get a list of available chart periods for this metro,--- expressed as date ranges which can be sent to the chart services.------ <http://www.last.fm/api/show/geo.getMetroWeeklyChartlist>-getMetroWeeklyChartlist :: Request f (Metro -> APIKey -> Ready)-getMetroWeeklyChartlist = api "geo.getMetroWeeklyChartlist"-{-# INLINE getMetroWeeklyChartlist #-}----- | Get a list of valid countries and metros for use in the other webservices------ Optional: 'country'------ <http://www.last.fm/api/show/geo.getMetros>-getMetros :: Request f (APIKey -> Ready)-getMetros = api "geo.getMetros"-{-# INLINE getMetros #-}----- | Get the most popular artists on Last.fm by country------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/geo.getTopArtists>-getTopArtists :: Request f (Country -> APIKey -> Ready)-getTopArtists = api "geo.getTopArtists"-{-# INLINE getTopArtists #-}----- | Get the most popular tracks on Last.fm last week by country------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/geo.getTopTracks>-getTopTracks :: Request f (Country -> APIKey -> Ready)-getTopTracks = api "geo.getTopTracks"-{-# INLINE getTopTracks #-}
− src/Network/Lastfm/Group.hs
@@ -1,74 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm group API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Group as Group--- @-module Network.Lastfm.Group- ( getHype, getMembers, getWeeklyAlbumChart, getWeeklyArtistChart, getWeeklyChartList, getWeeklyTrackChart- ) where--import Network.Lastfm.Request----- | Get the hype list for a group------ <http://www.last.fm/api/show/group.getHype>-getHype :: Request f (Group -> APIKey -> Ready)-getHype = api "group.getHype"-{-# INLINE getHype #-}----- | Get a list of members for this group.------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/group.getMembers>-getMembers :: Request f (Group -> APIKey -> Ready)-getMembers = api "group.getMembers"-{-# INLINE getMembers #-}----- | Get an album chart for a group, for a given date range.--- If no date range is supplied, it will return the most recent album chart for this group.------ Optional: 'from', 'to'------ <http://www.last.fm/api/show/group.getWeeklyAlbumChart>-getWeeklyAlbumChart :: Request f (Group -> APIKey -> Ready)-getWeeklyAlbumChart = api "group.getWeeklyAlbumChart"-{-# INLINE getWeeklyAlbumChart #-}----- | Get an artist chart for a group, for a given date range.--- If no date range is supplied, it will return the most recent album chart for this group.------ Optional: 'from', 'to'------ <http://www.last.fm/api/show/group.getWeeklyArtistChart>-getWeeklyArtistChart :: Request f (Group -> APIKey -> Ready)-getWeeklyArtistChart = api "group.getWeeklyArtistChart"-{-# INLINE getWeeklyArtistChart #-}----- | Get a list of available charts for this group, expressed as--- date ranges which can be sent to the chart services.------ <http://www.last.fm/api/show/group.getWeeklyChartList>-getWeeklyChartList :: Request f (Group -> APIKey -> Ready)-getWeeklyChartList = api "group.getWeeklyChartList"-{-# INLINE getWeeklyChartList #-}----- | Get a track chart for a group, for a given date range.--- If no date range is supplied, it will return the most recent album chart for this group.------ Optional: 'from', 'to'------ <http://www.last.fm/api/show/group.getWeeklyTrackChart>-getWeeklyTrackChart :: Request f (Group -> APIKey -> Ready)-getWeeklyTrackChart = api "group.getWeeklyTrackChart"-{-# INLINE getWeeklyTrackChart #-}
− src/Network/Lastfm/Internal.hs
@@ -1,163 +0,0 @@-{-# LANGUAGE CPP #-}-#if __GLASGOW_HASKELL__ >= 708-{-# LANGUAGE AutoDeriveTypeable #-}-#endif-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE KindSignatures #-}--- | liblastfm internals------ You shouldn't need to import this module unless you are doing something interesting.-module Network.Lastfm.Internal- ( Request(..)- , Format(..)- , Ready- , Sign- , R(..)- , wrap- , unwrap- , render- , coerce- , absorbQuery- , indexedWith- -- * Lenses- , host- , method- , query- ) where--import Control.Applicative-import Data.ByteString (ByteString)-import Data.Foldable (Foldable(..))-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as M-import Data.Monoid-import Data.Serialize (Serialize(..))-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import Data.Traversable (Traversable(..))-import Network.URI (escapeURIChar, isUnreserved)----- | Lastfm API request data type------ low-level representation-data R (f :: Format) = R- { _host :: {-# UNPACK #-} !Text- , _method :: {-# UNPACK #-} !ByteString- , _query :: !(Map Text Text)- }---- | Response format: either JSON or XML-data Format = JSON | XML---- | Request that is ready to be sent-data Ready---- | Request that requires signing procedure-data Sign----- | Lastfm API request data type------ @a@ is the authentication state. Can be 'Ready', which means this 'Request' is--- ready to be sent, or 'Sign', if the request signature hasn't been computed yet------ @f@ is the response format (liblastfm supports both 'JSON' and 'XML')-newtype Request f a = Request { unRequest :: Const (Dual (Endo (R f))) a }--instance Functor (Request f) where- fmap f (Request x) = Request (fmap f x)- {-# INLINE fmap #-}--instance Applicative (Request f) where- pure x = Request (pure x)- Request f <*> Request x = Request (f <*> x)- {-# INLINE (<*>) #-}--instance Foldable (Request f) where- foldMap _ (Request _) = mempty -- not sure why this instance isn't in base- {-# INLINE foldMap #-}--instance Traversable (Request f) where- traverse _ (Request (Const x)) = pure (Request (Const x)) -- and that- {-# INLINE traverse #-}---coerce :: Request f a -> Request f b-coerce (Request (Const x)) = Request (Const x)-{-# INLINE coerce #-}----- | Construct String from request for networking-render :: R f -> String-render R { _host = h, _query = q } =- T.unpack $ mconcat [h, "?", argie q]- where- argie = T.intercalate "&" . M.foldrWithKey (\k v m -> T.concat [escape k, "=", escape v] : m) []-- escape = T.concatMap (T.pack . escapeURIChar isUnreserved)----- | Wrapping to interesting 'Monoid' ('R' -> 'R') instance-wrap :: (R f -> R f) -> Request f a-wrap = Request . Const . Dual . Endo-{-# INLINE wrap #-}---- | Unwrapping from interesting 'Monoid' ('R' -> 'R') instance-unwrap :: Request f a -> R f -> R f-unwrap = appEndo . getDual . getConst . unRequest-{-# INLINE unwrap #-}----- | Absorbing a bunch of queries, useful in batch operations-absorbQuery :: Foldable t => t (Request f b) -> Request f a-absorbQuery rs = wrap $ \r ->- r { _query = _query r <> foldMap (_query . ($ rempty) . unwrap) rs }-{-# INLINE absorbQuery #-}---- | Transforming Request to the "array notation"-indexedWith :: Int -> Request f a -> Request f a-indexedWith n r = r <* wrap (\s ->- s { _query = M.mapKeys (\k -> k <> "[" <> T.pack (show n) <> "]") (_query s) })-{-# INLINE indexedWith #-}---- | Empty request-rempty :: R f-rempty = R mempty mempty mempty-{-# INLINE rempty #-}----- Miscellaneous instances--instance Serialize (R f) where- put r = do- put $ T.encodeUtf8 (_host r)- put $ _method r- put $ bimap T.encodeUtf8 T.encodeUtf8 (_query r)- get = do- h <- T.decodeUtf8 <$> get- m <- get- q <- bimap T.decodeUtf8 T.decodeUtf8 <$> get- return R { _host = h, _method = m, _query = q }--bimap :: (Ord s, Ord t) => (s -> t) -> (a -> b) -> Map s a -> Map t b-bimap f g = M.mapKeys f . M.map g-{-# INLINE bimap #-}----- | 'Request' '_host'-host :: Functor f => (Text -> f Text) -> R h -> f (R h)-host f r@R { _host = h } = (\h' -> r { _host = h' }) <$> f h-{-# INLINE host #-}---- | 'Request' HTTP '_method'-method :: Functor f => (ByteString -> f ByteString) -> R h -> f (R h)-method f r@R { _method = m } = (\m' -> r { _method = m' }) <$> f m-{-# INLINE method #-}---- | 'Request' '_query' string-query :: Functor f => (Map Text Text -> f (Map Text Text)) -> R h -> f (R h)-query f r@R { _query = q } = (\q' -> r { _query = q' }) <$> f q-{-# INLINE query #-}
− src/Network/Lastfm/Library.hs
@@ -1,125 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm library API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Library as Library--- @-module Network.Lastfm.Library- ( addAlbum, albumItem, addArtist, artistItem, addTrack- , getAlbums, getArtists, getTracks- , removeAlbum, removeArtist, removeScrobble, removeTrack- ) where--import Control.Applicative-import Data.List.NonEmpty (NonEmpty)-import qualified Data.List.NonEmpty as N--import Network.Lastfm.Internal (absorbQuery, indexedWith, wrap)-import Network.Lastfm.Request------- | Add an album or collection of albums to a user's Last.fm library------ <http://www.last.fm/api/show/library.addAlbum>-addAlbum :: NonEmpty (Request f LibraryAlbum) -> Request f (APIKey -> SessionKey -> Sign)-addAlbum batch = api "library.addAlbum" <* items <* post- where- items = absorbQuery (N.zipWith indexedWith (N.fromList [0..]) batch)- {-# INLINE items #-}-{-# INLINE addAlbum #-}---- | What artist to add to library?-albumItem :: Request f (Artist -> Album -> LibraryAlbum)-albumItem = wrap id-{-# INLINE albumItem #-}----- | Add an artist to a user's Last.fm library------ <http://www.last.fm/api/show/library.addArtist>-addArtist :: NonEmpty (Request f LibraryArtist) -> Request f (APIKey -> SessionKey -> Sign)-addArtist batch = api "library.addArtist" <* items <* post- where- items = absorbQuery (N.zipWith indexedWith (N.fromList [0..]) batch)- {-# INLINE items #-}-{-# INLINE addArtist #-}---- | What album to add to library?-artistItem :: Request f (Artist -> LibraryArtist)-artistItem = wrap id-{-# INLINE artistItem #-}----- | Add a track to a user's Last.fm library------ <http://www.last.fm/api/show/library.addTrack>-addTrack :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)-addTrack = api "library.addTrack" <* post-{-# INLINE addTrack #-}----- | A paginated list of all the albums in a user's library, with play counts and tag counts.------ Optional: 'artist', 'limit', 'page'------ <http://www.last.fm/api/show/library.getAlbums>-getAlbums :: Request f (User -> APIKey -> Ready)-getAlbums = api "library.getAlbums"-{-# INLINE getAlbums #-}----- | A paginated list of all the artists in a user's library, with play counts and tag counts.------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/library.getArtists>-getArtists :: Request f (User -> APIKey -> Ready)-getArtists = api "library.getArtists"-{-# INLINE getArtists #-}----- | A paginated list of all the tracks in a user's library, with play counts and tag counts.------ Optional: 'artist', 'album', 'page', 'limit'------ <http://www.last.fm/api/show/library.getTracks>-getTracks :: Request f (User -> APIKey -> Ready)-getTracks = api "library.getTracks"-{-# INLINE getTracks #-}----- | Remove an album from a user's Last.fm library------ <http://www.last.fm/api/show/library.removeAlbum>-removeAlbum :: Request f (Artist -> Album -> APIKey -> SessionKey -> Sign)-removeAlbum = api "library.removeAlbum" <* post-{-# INLINE removeAlbum #-}----- | Remove an artist from a user's Last.fm library------ <http://www.last.fm/api/show/library.removeArtist>-removeArtist :: Request f (Artist -> APIKey -> SessionKey -> Sign)-removeArtist = api "library.removeArtist" <* post-{-# INLINE removeArtist #-}----- | Remove a scrobble from a user's Last.fm library------ <http://www.last.fm/api/show/library.removeScrobble>-removeScrobble :: Request f (Artist -> Track -> Timestamp -> APIKey -> SessionKey -> Sign)-removeScrobble = api "library.removeScrobble" <* post-{-# INLINE removeScrobble #-}----- | Remove a track from a user's Last.fm library------ <http://www.last.fm/api/show/library.removeTrack>-removeTrack :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)-removeTrack = api "library.removeTrack" <* post-{-# INLINE removeTrack #-}
− src/Network/Lastfm/Playlist.hs
@@ -1,34 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm playlist API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Playlist as Playlist--- @-module Network.Lastfm.Playlist- ( addTrack, create- ) where--import Control.Applicative--import Network.Lastfm.Request----- | Add a track to a Last.fm user's playlist------ <http://www.last.fm/api/show/playlist.addTrack>-addTrack :: Request f (Playlist -> Artist -> Track -> APIKey -> SessionKey -> Sign)-addTrack = api "playlist.addTrack" <* post-{-# INLINE addTrack #-}----- | Create a Last.fm playlist on behalf of a user------ Optional: 'title', 'description'------ <http://www.last.fm/api/show/playlist.create>-create :: Request f (APIKey -> SessionKey -> Sign)-create = api "playlist.create" <* post-{-# INLINE create #-}
− src/Network/Lastfm/Radio.hs
@@ -1,45 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm radio API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Radio as Radio--- @-module Network.Lastfm.Radio- ( getPlaylist, search, tune- ) where--import Control.Applicative--import Network.Lastfm.Request----- | Fetch new radio content periodically in an XSPF format.------ Optional: 'discovery', 'rtp', 'buyLinks'------ <http://www.last.fm/api/show/radio.getPlaylist>-getPlaylist :: Request f (Multiplier -> Bitrate -> APIKey -> SessionKey -> Sign)-getPlaylist = api "radio.getPlaylist"-{-# INLINE getPlaylist #-}----- | Resolve the name of a resource into a station depending on which resource--- it is most likely to represent.------ <http://www.last.fm/api/show/radio.search>-search :: Request f (Name -> APIKey -> Ready)-search = api "radio.search"-{-# INLINE search #-}----- | Tune in to a Last.fm radio station.------ Optional: 'language'------ <http://www.last.fm/api/show/radio.tune>-tune :: Request f (Station -> APIKey -> SessionKey -> Sign)-tune = api "radio.tune" <* post-{-# INLINE tune #-}
− src/Network/Lastfm/Request.hs
@@ -1,494 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}--- | Request construction-module Network.Lastfm.Request- ( -- * Request- Request, R, Ready, Sign, Format(..)- -- * Request major parameters- , api, post, get, json, xml, APIKey, apiKey, SessionKey, sessionKey- -- * Request minor parameters- , Token, token, Callback, callback- , Artist, artist, artists, Album, album, MBID, mbid- , Country, country, Autocorrect, autocorrect- , Event, event, Status(..), status- , From, from, To, to, Group, group- , Language, language, Distance, distance- , Longitude, longitude, Latitude, latitude, Location, location- , Start, start, End, end, Festivals, festivalsonly- , StartTimestamp, startTimestamp, EndTimestamp, endTimestamp- , Metro, metro, Tag, tags, tag, Track, track, Timestamp , timestamp, Fingerprint, fingerprint- , AlbumArtist, albumArtist, Duration, duration, TrackNumber, trackNumber- , Playlist, playlist, Title, title, Description, description- , ChosenByUser, chosenByUser, Context, context, StreamId, streamId- , RecentTracks, recentTracks- , Recipient, recipient, Username, username, User, user, Password, password- , Public, public, Message, message, Page, page, Limit, limit- , TaggingType, taggingType, UseRecs, useRecs, Venue, venue, VenueName, venueName- , Discovery, discovery, RTP, rtp, BuyLinks, buyLinks, Multiplier(..), multiplier- , Bitrate(..), bitrate, Name, name, Station, station- , Targeted, comparison, Scrobble, LibraryAlbum, LibraryArtist- ) where--import Control.Applicative-import Data.Int (Int64)-import Data.Monoid ((<>), mempty)--import qualified Data.Map.Strict as M-import Data.Text (Text)-import qualified Data.Text as T--import Network.Lastfm.Internal---class Argument a where- add :: Text -> a -> Request f b- add k v = wrap $ \r@R { _query = q } -> r { _query = M.insert k (toText v) q }- {-# INLINE add #-}-- toText :: a -> Text--instance Argument Text where- toText = id- {-# INLINE toText #-}--instance Argument Bool where- toText b = if b then "1" else "0"- {-# INLINE toText #-}--instance Argument Int64 where- toText = T.pack . show- {-# INLINE toText #-}--instance Argument a => Argument [a] where- toText = T.intercalate "," . map toText- {-# INLINE toText #-}---data APIKey-data SessionKey-data Token-data Callback----- | Change request API method------ Primarily used in API call wrappers, not intended for usage by library user-api :: Text -> Request f a-api = add "method"-{-# INLINE api #-}---- | Change html _method to GET------ Primarily used in API call wrappers, not intended for usage by library user-get :: Request f a-get = wrap $ \r -> r { _method = "GET" }-{-# INLINE get #-}---- | Change html _method to POST------ Primarily used in API call wrappers, not intended for usage by library user-post :: Request f a-post = wrap $ \r -> r { _method = "POST" }-{-# INLINE post #-}---- | Change API response format to JSON------ This is a little helper. It's actually enough--- to specialize Format manually-json :: Request JSON a-json = wrap id-{-# INLINE json #-}---- | Change API response format to XML------ This is a little helper. It's actually enough--- to specialize Format manually-xml :: Request XML a-xml = wrap id-{-# INLINE xml #-}---- | Change request API key-apiKey :: Text -> Request f APIKey-apiKey = add "api_key"-{-# INLINE apiKey #-}---- | Change request session key-sessionKey :: Text -> Request f SessionKey-sessionKey = add "sk"-{-# INLINE sessionKey #-}---- | Add token parameter-token :: Text -> Request f Token-token = add "token"-{-# INLINE token #-}---- | Add callback link parameter-callback :: Text -> Request f Callback-callback = add "cb"-{-# INLINE callback #-}---data Artist-data Album-data MBID-data Country-data Language-data Tag-data Autocorrect-data Page-data Limit-data Message-data Public-data Recipient-data Username-data User-data Password-data Status = Attending | Maybe | NotAttending-data Event-data Festivals-data Longitude-data Latitude-data Location-data Distance-data Metro-data Start-data End-data StartTimestamp-data EndTimestamp-data From-data To-data Playlist-data Title-data Description-data Track-data Timestamp-data Fingerprint-data AlbumArtist-data Context-data StreamId-data Duration-data TrackNumber-data ChosenByUser-data TaggingType-data RecentTracks-data UseRecs-data Scrobble-data Group-data Venue-data VenueName-data Multiplier = M1 | M2-data Bitrate = B64 | B128-data Name-data Station-data Discovery-data RTP-data BuyLinks-data LibraryAlbum-data LibraryArtist----- | Add artist parameter-artist :: Text -> Request f Artist-artist = add "artist"-{-# INLINE artist #-}---- | Add artists parameter-artists :: [Text] -> Request f [Artist]-artists = add "artists"-{-# INLINE artists #-}---- | Add album parameter-album :: Text -> Request f Album-album = add "album"-{-# INLINE album #-}---- | Add MBID parameter-mbid :: Text -> Request f MBID-mbid = add "mbid"-{-# INLINE mbid #-}---- | Add country parameter-country :: Text -> Request f Country-country = add "country"-{-# INLINE country #-}---- | Add language parameter-language :: Text -> Request f Language-language = add "lang"-{-# INLINE language #-}---- | Add tags parameter-tags :: [Text] -> Request f [Tag]-tags = add "tags"-{-# INLINE tags #-}---- | Add tag parameter-tag :: Text -> Request f Tag-tag = add "tag"-{-# INLINE tag #-}---- | Add autocorrect parameter-autocorrect :: Bool -> Request f Autocorrect-autocorrect = add "tags"-{-# INLINE autocorrect #-}---- | Add page parameter-page :: Int64 -> Request f Page-page = add "page"-{-# INLINE page #-}---- | Add limit parameter-limit :: Int64 -> Request f Limit-limit = add "limit"-{-# INLINE limit #-}---- | Add message parameter-message :: Text -> Request f Message-message = add "message"-{-# INLINE message #-}---- | Add public parameter-public :: Bool -> Request f Public-public = add "public"-{-# INLINE public #-}---- | Add recipient parameter-recipient :: Text -> Request f Recipient-recipient = add "recipient"-{-# INLINE recipient #-}---- | Add username parameter-username :: Text -> Request f Username-username = add "username"-{-# INLINE username #-}---- | Add user parameter-user :: Text -> Request f User-user = add "user"-{-# INLINE user #-}---- | Add password parameter-password :: Text -> Request f Password-password = add "password"-{-# INLINE password #-}---- | Add status parameter-status :: Status -> Request f Status-status = add "status" . T.pack . \s -> case s of- Attending -> "0"- Maybe -> "1"- _ -> "2"-{-# INLINE status #-}---- | Add event parameter-event :: Int64 -> Request f Event-event = add "event"-{-# INLINE event #-}---- | Add festivalsonly parameter-festivalsonly :: Bool -> Request f Festivals-festivalsonly = add "festivalsonly"-{-# INLINE festivalsonly #-}---- | Add longitude parameter-longitude :: Text -> Request f Longitude-longitude = add "longitude"-{-# INLINE longitude #-}---- | Add latitude parameter-latitude :: Text -> Request f Latitude-latitude = add "latitude"-{-# INLINE latitude #-}---- | Add location parameter-location :: Text -> Request f Location-location = add "location"-{-# INLINE location #-}---- | Add distance parameter-distance :: Int64 -> Request f Distance-distance = add "distance"-{-# INLINE distance #-}---- | Add venue parameter-venue :: Int64 -> Request f Venue-venue = add "venue"-{-# INLINE venue #-}---- | Add venue parameter-venueName :: Text -> Request f VenueName-venueName = add "venue"-{-# INLINE venueName #-}---- | Add metro parameter-metro :: Text -> Request f Metro-metro = add "metro"-{-# INLINE metro #-}---- | Add start parameter-start :: Int64 -> Request f Start-start = add "start"-{-# INLINE start #-}---- | Add end parameter-end :: Int64 -> Request f End-end = add "end"-{-# INLINE end #-}---- | Add startTimestamp parameter-startTimestamp :: Int64 -> Request f StartTimestamp-startTimestamp = add "startTimestamp"-{-# INLINE startTimestamp #-}---- | Add endTimestamp parameter-endTimestamp :: Int64 -> Request f EndTimestamp-endTimestamp = add "endTimestamp"-{-# INLINE endTimestamp #-}---- | Add from parameter-from :: Int64 -> Request f From-from = add "from"-{-# INLINE from #-}---- | Add to parameter-to :: Int64 -> Request f To-to = add "to"-{-# INLINE to #-}---- | Add track parameter-track :: Text -> Request f Track-track = add "track"-{-# INLINE track #-}---- | Add timestamp parameter-timestamp :: Int64 -> Request f Timestamp-timestamp = add "timestamp"-{-# INLINE timestamp #-}---- | Add playlistID parameter-playlist :: Int64 -> Request f Playlist-playlist = add "playlistID"-{-# INLINE playlist #-}---- | Add title parameter-title :: Text -> Request f Title-title = add "title"-{-# INLINE title #-}---- | Add description parameter-description :: Text -> Request f Description-description = add "description"-{-# INLINE description #-}---- | Add fingerprint parameter-fingerprint :: Int64 -> Request f Fingerprint-fingerprint = add "fingerprintid"-{-# INLINE fingerprint #-}---- | Add albumArtist parameter-albumArtist :: Text -> Request f AlbumArtist-albumArtist = add "albumArtist"-{-# INLINE albumArtist #-}---- | Add context parameter-context :: Text -> Request f Context-context = add "context"-{-# INLINE context #-}---- | Add streamId parameter-streamId :: Int64 -> Request f StreamId-streamId = add "streamId"-{-# INLINE streamId #-}---- | Add duration parameter-duration :: Int64 -> Request f Duration-duration = add "duration"-{-# INLINE duration #-}---- | Add trackNumber parameter-trackNumber :: Int64 -> Request f TrackNumber-trackNumber = add "trackNumber"-{-# INLINE trackNumber #-}---- | Add chosenByUser parameter-chosenByUser :: Bool -> Request f ChosenByUser-chosenByUser = add "chosenByUser"-{-# INLINE chosenByUser #-}---- | Add taggingType parameter-taggingType :: Text -> Request f TaggingType-taggingType = add "taggingtype"-{-# INLINE taggingType #-}---- | Add recentTracks parameter-recentTracks :: Bool -> Request f RecentTracks-recentTracks = add "recentTracks"-{-# INLINE recentTracks #-}---- | Add useRecs parameter-useRecs :: Bool -> Request f UseRecs-useRecs = add "useRecs"-{-# INLINE useRecs #-}---- | Add group parameter-group :: Text -> Request f Group-group = add "group"-{-# INLINE group #-}---- | Add multiplier parameter-multiplier :: Multiplier -> Request f Multiplier-multiplier m = case m of- M1 -> add "speed_multiplier" (T.pack "1.0")- M2 -> add "speed_multiplier" (T.pack "2.0")-{-# INLINE multiplier #-}---- | Add bitrate parameter-bitrate :: Bitrate -> Request f Bitrate-bitrate b = case b of- B64 -> add "bitrate" (64 :: Int64)- B128 -> add "bitrate" (128 :: Int64)-{-# INLINE bitrate #-}---- | Add name parameter-name :: Text -> Request f Name-name = add "name"-{-# INLINE name #-}---- | Add station parameter-station :: Text -> Request f Station-station = add "station"-{-# INLINE station #-}---- | Add group parameter-discovery :: Bool -> Request f Discovery-discovery = add "discovery"-{-# INLINE discovery #-}---- | Add rtp parameter-rtp :: Bool -> Request f RTP-rtp = add "rtp"-{-# INLINE rtp #-}---- | Add buyLinks parameter-buyLinks :: Bool -> Request f BuyLinks-buyLinks = add "buyLinks"-{-# INLINE buyLinks #-}---class Targeted a where- target :: Request f a -> Text--instance Targeted [Artist] where- target _ = "artists"- {-# INLINE target #-}--instance Targeted User where- target _ = "user"- {-# INLINE target #-}---- | Add comparison parameter-comparison :: Targeted a => Int64 -> Request f a -> Request f a-comparison n t = let z = target t in- add ("type" <> toText n) z <*> add ("value" <> toText n) (_query (unwrap t (R mempty mempty mempty)) M.! z)-{-# INLINE comparison #-}
− src/Network/Lastfm/Response.hs
@@ -1,249 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Request sending and Response parsing-module Network.Lastfm.Response- ( -- * Compute request signature- -- $sign- Secret(..)- , sign- -- * Perform requests- , Connection- , withConnection- , newConnection- , closeConnection- , lastfm- , lastfm_- , Supported- , Format(..)- -- ** Errors- , LastfmError(..)- , _LastfmBadResponse- , _LastfmEncodedError- , _LastfmHttpError-#ifdef TEST- , parse- , md5-#endif- ) where--import Control.Applicative-import Control.Exception (SomeException(..), Exception(..), bracket, catch)-import Crypto.Classes (hash')-import Data.Aeson ((.:), Value(..), decode)-import Data.Aeson.Types (parseMaybe)-import qualified Data.ByteString.Lazy as Lazy-import qualified Data.ByteString as Strict-import Data.Digest.Pure.MD5 (MD5Digest)-import Data.Map (Map)-import qualified Data.Map as M-import Data.Monoid-import Data.Profunctor (Choice, dimap, right')-import Data.String (IsString(..))-import Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Data.Text.Read as T-import Data.Typeable (Typeable, cast)-import qualified Network.HTTP.Client as Http-import qualified Network.HTTP.Client.TLS as Http-import Text.XML (Document, parseLBS, def)-import Text.XML.Cursor--import Network.Lastfm.Internal----- $sign------ The signature is required for every--- authenticated API request. Basically,--- every such request appends the md5 footprint--- of its arguments to the query as--- described at <http://www.last.fm/api/authspec#8>----- | 'Supported' provides parsing for the chosen 'Format'------ 'JSON' is parsed to 'Value' type from aeson, while 'XML'--- is parsed to 'Document' from xml-conduit-class Supported f r | f -> r, r -> f where- prepareRequest :: R f -> R f- parseResponseBody :: Lazy.ByteString -> Maybe r- parseResponseEncodedError :: r -> Maybe LastfmError--instance Supported JSON Value where- prepareRequest r = r { _query = M.singleton "format" "json" `M.union` _query r }- parseResponseBody = decode- parseResponseEncodedError = parseMaybe $ \(Object o) -> do- code <- o .: "error"- msg <- o .: "message"- return (LastfmEncodedError code msg)--instance Supported XML Document where- prepareRequest = id- parseResponseBody = either (const Nothing) Just . parseLBS def- parseResponseEncodedError doc = case fromDocument doc of- cur- | [mcode] <- cur $| element "lfm" >=> child >=> element "error" >=> attribute "code"- , Right (code, _) <- T.decimal mcode- , [msg] <- cur $| element "lfm" >=> child >=> element "error" >=> child >=> content- -> Just (LastfmEncodedError code (T.strip msg))- |- otherwise -> Nothing--parse :: Supported f r => Lazy.ByteString -> Either LastfmError r-parse body = case parseResponseBody body of- Just v- | Just e <- parseResponseEncodedError v -> Left e- | otherwise -> Right v- Nothing -> Left (LastfmBadResponse body)--base :: R f-base = R- { _host = "https://ws.audioscrobbler.com/2.0/"- , _method = "GET"- , _query = mempty- }----- | Different ways last.fm response can be unusable-data LastfmError =- -- | last.fm thinks it responded with something legible, but it really isn't- LastfmBadResponse Lazy.ByteString- -- | last.fm error code and message string- | LastfmEncodedError Int Text- -- | wrapped http-conduit exception- | LastfmHttpError Http.HttpException- deriving (Show, Typeable)---- | Admittedly, this isn't the best 'Eq' instance ever--- but not having 'Eq' 'C.HttpException' does not leave much a choice-instance Eq LastfmError where- LastfmBadResponse bs == LastfmBadResponse bs' = bs == bs'- LastfmEncodedError e s == LastfmEncodedError e' t = e == e' && s == t- LastfmHttpError _ == LastfmHttpError _ = True- _ == _ = False--instance Exception LastfmError where- fromException e@(SomeException se)- | Just e' <- fromException e = Just (LastfmHttpError e')- | otherwise = cast se--class AsLastfmError t where- _LastfmError :: (Choice p, Applicative m) => p LastfmError (m LastfmError) -> p t (m t)--instance AsLastfmError LastfmError where- _LastfmError = id- {-# INLINE _LastfmError #-}--instance AsLastfmError SomeException where- _LastfmError = dimap (\e -> maybe (Left e) Right (fromException e)) (either pure (fmap toException)) . right'- {-# INLINE _LastfmError #-}---- | This is a @ Prism' 'LastfmError' 'Lazy.ByteString' @ in disguise-_LastfmBadResponse- :: (Choice p, Applicative m, AsLastfmError e)- => p Lazy.ByteString (m Lazy.ByteString) -> p e (m e)-_LastfmBadResponse = _LastfmError . dimap go (either pure (fmap LastfmBadResponse)) . right' where- go (LastfmBadResponse bs) = Right bs- go x = Left x- {-# INLINE go #-}-{-# INLINE _LastfmBadResponse #-}---- | This is a @ Prism' 'LastfmError' ('Int', 'String') @ in disguise-_LastfmEncodedError- :: (Choice p, Applicative m, AsLastfmError e)- => p (Int, Text) (m (Int, Text)) -> p e (m e)-_LastfmEncodedError = _LastfmError . dimap go (either pure (fmap (uncurry LastfmEncodedError))) . right' where- go (LastfmEncodedError n v) = Right (n, v)- go x = Left x- {-# INLINE go #-}-{-# INLINE _LastfmEncodedError #-}---- | This is a @ Prism' 'LastfmError' 'C.HttpException' @ in disguise-_LastfmHttpError- :: (Choice p, Applicative m, AsLastfmError e)- => p Http.HttpException (m Http.HttpException) -> p e (m e)-_LastfmHttpError = _LastfmError . dimap go (either pure (fmap LastfmHttpError)) . right' where- go (LastfmHttpError e) = Right e- go x = Left x- {-# INLINE go #-}-{-# INLINE _LastfmHttpError #-}----- | Application secret-newtype Secret = Secret Text deriving (Show, Eq, Typeable)--instance IsString Secret where- fromString = Secret . fromString---- | Sign the 'Request' with the 'Secret' so it's ready to be sent-sign :: Secret -> Request f Sign -> Request f Ready-sign s = coerce . (<* signature)- where- signature = wrap $- \r@R { _query = q } -> r { _query = apiSig s . authToken $ q }--authToken :: Map Text Text -> Map Text Text-authToken q = maybe q (M.delete "password") $ do- password <- M.lookup "password" q- username <- M.lookup "username" q- return (M.insert "authToken" (md5 (username <> md5 password)) q)--apiSig :: Secret -> Map Text Text -> Map Text Text-apiSig (Secret s) q = M.insert "api_sig" (signer (foldr M.delete q ["format", "callback"])) q- where- signer = md5 . M.foldrWithKey (\k v xs -> k <> v <> xs) s---- | Get supplied string md5 hash hex representation-md5 :: Text -> Text-md5 = T.pack . show . (hash' :: Strict.ByteString -> MD5Digest) . T.encodeUtf8----- | Lastfm connection manager-newtype Connection = Connection Http.Manager---- | Creating an HTTPS connection manager is expensive; it's advised to use--- a single 'Connection' for all communications with last.fm-withConnection :: (Connection -> IO a) -> IO a-withConnection = bracket newConnection closeConnection---- | Create a 'Connection'-newConnection :: IO Connection-newConnection = Connection <$> Http.newManager Http.tlsManagerSettings---- | Close a 'Connection'-closeConnection :: Connection -> IO ()-closeConnection (Connection man) = Http.closeManager man---- | Perform the 'Request' and parse the response-lastfm :: Supported f r => Connection -> Request f Ready -> IO (Either LastfmError r)-lastfm man = lastfmWith man parse . finalize---- | Perform the 'Request' ignoring any responses-lastfm_ :: Supported f r => Connection -> Request f Ready -> IO (Either LastfmError ())-lastfm_ man = lastfmWith man (\_ -> Right ()) . finalize---- | Send the 'R' and parse the 'Response' with the supplied parser-lastfmWith- :: Supported f r- => Connection- -> (Lazy.ByteString -> Either LastfmError a)- -> R f- -> IO (Either LastfmError a)-lastfmWith (Connection man) p r = do- req <- Http.parseUrl (render r)- let req' = req- { Http.method = _method r- , Http.responseTimeout = Just 10000000- }- p . Http.responseBody <$> Http.httpLbs req' man- `catch`- (return . Left)---- | Get the 'R' from the 'Request'-finalize :: Supported f r => Request f Ready -> R f-finalize x = (prepareRequest . unwrap x) base
− src/Network/Lastfm/Tag.hs
@@ -1,101 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm tag API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Tag as Tag--- @-module Network.Lastfm.Tag- ( getInfo, getSimilar, getTopAlbums, getTopArtists, getTopTags, getTopTracks- , getWeeklyArtistChart, getWeeklyChartList, search- ) where--import Network.Lastfm.Request----- | Get the metadata for a tag------ Optional: language------ <http://www.last.fm/api/show/tag.getInfo>-getInfo :: Request f (Tag -> APIKey -> Ready)-getInfo = api "tag.getInfo"-{-# INLINE getInfo #-}----- | Search for tags similar to this one. Returns tags ranked by similarity, based on listening data.------ <http://www.last.fm/api/show/tag.getSimilar>-getSimilar :: Request f (Tag -> APIKey -> Ready)-getSimilar = api "tag.getSimilar"-{-# INLINE getSimilar #-}----- | Get the top albums tagged by this tag, ordered by tag count.------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/tag.getTopAlbums>-getTopAlbums :: Request f (Tag -> APIKey -> Ready)-getTopAlbums = api "tag.getTopAlbums"-{-# INLINE getTopAlbums #-}----- | Get the top artists tagged by this tag, ordered by tag count.------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/tag.getTopArtists>-getTopArtists :: Request f (Tag -> APIKey -> Ready)-getTopArtists = api "tag.getTopArtists"-{-# INLINE getTopArtists #-}----- | Fetches the top global tags on Last.fm, sorted by popularity (number of times used)------ <http://www.last.fm/api/show/tag.getTopTags>-getTopTags :: Request f (APIKey -> Ready)-getTopTags = api "tag.getTopTags"-{-# INLINE getTopTags #-}----- | Get the top tracks tagged by this tag, ordered by tag count.------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/tag.getTopTracks>-getTopTracks :: Request f (Tag -> APIKey -> Ready)-getTopTracks = api "tag.getTopTracks"-{-# INLINE getTopTracks #-}----- | Get an artist chart for a tag, for a given date range.--- If no date range is supplied, it will return the most recent artist chart for this tag.------ Optional: 'from', 'to', 'limit'------ <http://www.last.fm/api/show/tag.getWeeklyArtistChart>-getWeeklyArtistChart :: Request f (Tag -> APIKey -> Ready)-getWeeklyArtistChart = api "tag.getWeeklyArtistChart"-{-# INLINE getWeeklyArtistChart #-}----- | Get a list of available charts for this tag, expressed as--- date ranges which can be sent to the chart services.------ <http://www.last.fm/api/show/tag.getWeeklyChartList>-getWeeklyChartList :: Request f (Tag -> APIKey -> Ready)-getWeeklyChartList = api "tag.getWeeklyChartList"-{-# INLINE getWeeklyChartList #-}----- | Search for a tag by name. Returns matches sorted by relevance.------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/tag.search>-search :: Request f (Tag -> APIKey -> Ready)-search = api "tag.search"-{-# INLINE search #-}
− src/Network/Lastfm/Tasteometer.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm tasteometer API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Tasteometer as Tasteometer--- @-module Network.Lastfm.Tasteometer where--import Control.Applicative--import Network.Lastfm.Request----- | Get a Tasteometer score from two inputs, along with a list of shared artists.--- If the input is a user some additional information is returned.------ Optional: 'limit'------ <http://www.last.fm/api/show/tasteometer.compare>-compare :: (Targeted u, Targeted v) => Request f u -> Request f v -> Request f (APIKey -> Ready)-compare u v = api "tasteometer.compare" <* comparison 1 u <* comparison 2 v-{-# INLINE compare #-}
− src/Network/Lastfm/Track.hs
@@ -1,221 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm track API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Track as Track--- @-module Network.Lastfm.Track- ( ArtistTrackOrMBID- , addTags, ban, getBuyLinks, getCorrection, getFingerprintMetadata- , getInfo, getShouts, getSimilar, getTags, getTopFans- , getTopTags, love, removeTag, scrobble, item- , search, share, unban, unlove, updateNowPlaying- ) where--import Control.Applicative-import Data.List.NonEmpty (NonEmpty)-import qualified Data.List.NonEmpty as N--import Network.Lastfm.Internal (absorbQuery, indexedWith, wrap)-import Network.Lastfm.Request----- | Unify ('Artist' -> 'Track' -> …) and ('MBID' -> …)-class ArtistTrackOrMBID a--instance ArtistTrackOrMBID (MBID -> APIKey -> Ready)-instance ArtistTrackOrMBID (Artist -> Track -> APIKey -> Ready)----- | Tag a track using a list of user supplied tags.------ <http://www.last.fm/api/show/track.addTags>-addTags :: Request f (Artist -> Track -> [Tag] -> APIKey -> SessionKey -> Sign)-addTags = api "track.addTags" <* post-{-# INLINE addTags #-}----- | Ban a track for a given user profile.------ <http://www.last.fm/api/show/track.ban>-ban :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)-ban = api "track.ban" <* post-{-# INLINE ban #-}----- | Get a list of Buy Links for a particular track.------ Optional: 'autocorrect'------ <http://www.last.fm/api/show/track.getBuylinks>-getBuyLinks :: ArtistTrackOrMBID t => Request f (Country -> t)-getBuyLinks = api "track.getBuyLinks"-{-# INLINE getBuyLinks #-}----- | Use the last.fm corrections data to check whether--- the supplied track has a correction to a canonical track.------ <http://www.last.fm/api/show/track.getCorrection>-getCorrection :: Request f (Artist -> Track -> APIKey -> Ready)-getCorrection = api "track.getCorrection"-{-# INLINE getCorrection #-}----- | Retrieve track metadata associated with a fingerprint id--- generated by the Last.fm Fingerprinter. Returns track--- elements, along with a 'rank' value between 0 and 1 reflecting the confidence for each match.------ <http://www.last.fm/api/show/track.getFingerprintMetadata>-getFingerprintMetadata :: Request f (Fingerprint -> APIKey -> Ready)-getFingerprintMetadata = api "track.getFingerprintMetadata"-{-# INLINE getFingerprintMetadata #-}----- | Get the metadata for a track on Last.fm.------ Optional: 'autocorrect', 'username'------ <http://www.last.fm/api/show/track.getInfo>-getInfo :: ArtistTrackOrMBID t => Request f t-getInfo = api "track.getInfo"-{-# INLINE getInfo #-}----- | Get shouts for this track. Also available as an rss feed.------ Optional: 'autocorrect', 'limit', 'page'------ <http://www.last.fm/api/show/track.getShouts>-getShouts :: ArtistTrackOrMBID t => Request f t-getShouts = api "track.getShouts"-{-# INLINE getShouts #-}----- | Get the similar tracks for this track on Last.fm, based on listening data.------ Optional: 'autocorrect', 'limit'------ <http://www.last.fm/api/show/track.getSimilar>-getSimilar :: ArtistTrackOrMBID t => Request f t-getSimilar = api "track.getSimilar"-{-# INLINE getSimilar #-}----- | Get the tags applied by an individual user to a track on Last.fm.------ Optional: 'autocorrect', 'user'------ <http://www.last.fm/api/show/track.getTags>-getTags :: ArtistTrackOrMBID t => Request f t-getTags = api "track.getTags"-{-# INLINE getTags #-}----- | Get the top fans for this track on Last.fm, based on listening data.------ Optional: 'autocorrect'------ <http://www.last.fm/api/show/track.getTopFans>-getTopFans :: ArtistTrackOrMBID t => Request f t-getTopFans = api "track.getTopFans"-{-# INLINE getTopFans #-}----- | Get the top tags for this track on Last.fm, ordered by tag count.------ Optional: 'autocorrect'------ <http://www.last.fm/api/show/track.getTopTags>-getTopTags :: ArtistTrackOrMBID t => Request f t-getTopTags = api "track.getTopTags"-{-# INLINE getTopTags #-}----- | Love a track for a user profile.------ <http://www.last.fm/api/show/track.love>-love :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)-love = api "track.love" <* post-{-# INLINE love #-}----- | Remove a user's tag from a track.------ <http://www.last.fm/api/show/track.removeTag>-removeTag :: Request f (Artist -> Track -> Tag -> APIKey -> SessionKey -> Sign)-removeTag = api "track.removeTag" <* post-{-# INLINE removeTag #-}----- | Add played tracks to the user profile.------ Scrobbles 50 first list elements------ <http://www.last.fm/api/show/track.scrobble>-scrobble :: NonEmpty (Request f Scrobble) -> Request f (APIKey -> SessionKey -> Sign)-scrobble batch = api "track.scrobble" <* items <* post- where- items = absorbQuery (N.zipWith indexedWith (N.fromList [0..49]) batch)- {-# INLINE items #-}-{-# INLINE scrobble #-}---- | What track to scrobble?------ Optional: 'album', 'albumArtist', 'chosenByUser', 'context',--- 'duration', 'mbid', 'streamId', 'trackNumber'-item :: Request f (Artist -> Track -> Timestamp -> Scrobble)-item = wrap id-{-# INLINE item #-}----- | Search for a track by track name. Returns track matches sorted by relevance.------ Optional: 'artist', 'limit', 'page'------ <http://www.last.fm/api/show/track.search>-search :: Request f (Track -> APIKey -> Ready)-search = api "track.search"-{-# INLINE search #-}----- | Share a track twith one or more Last.fm users or other friends.------ Optional: 'public', 'message', 'recipient'------ <http://www.last.fm/api/show/track.share>-share :: Request f (Artist -> Track -> Recipient -> APIKey -> SessionKey -> Sign)-share = api "track.share" <* post-{-# INLINE share #-}----- | Unban a track for a user profile.------ <http://www.last.fm/api/show/track.unban>-unban :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)-unban = api "track.unban" <* post-{-# INLINE unban #-}----- | Unlove a track for a user profile.------ <http://www.last.fm/api/show/track.unlove>-unlove :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)-unlove = api "track.unlove" <* post-{-# INLINE unlove #-}----- | Used to notify Last.fm that a user has started listening--- to a track. Parameter names are case sensitive.------ Optional: 'album', 'albumArtist', 'context',--- 'duration', 'mbid', 'trackNumber'------ <http://www.last.fm/api/show/track.updateNowPlaying>-updateNowPlaying :: Request f (Artist -> Track -> APIKey -> SessionKey -> Sign)-updateNowPlaying = api "track.updateNowPlaying" <* post-{-# INLINE updateNowPlaying #-}
− src/Network/Lastfm/User.hs
@@ -1,275 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm user API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.User as User--- @-module Network.Lastfm.User- ( getArtistTracks, getBannedTracks, getEvents, getFriends- , getInfo, getLovedTracks, getNeighbours, getNewReleases- , getPastEvents, getPersonalTags, getPlaylists, getRecentStations- , getRecentTracks, getRecommendedArtists, getRecommendedEvents- , getShouts, getTopAlbums, getTopArtists, getTopTags- , getTopTracks, getWeeklyAlbumChart, getWeeklyArtistChart- , getWeeklyChartList, getWeeklyTrackChart, shout- ) where--import Control.Applicative--import Network.Lastfm.Request----- | Get a list of tracks by a given artist scrobbled by this user--- , including scrobble time. Can be limited to specific timeranges, defaults to all time.------ Optional: 'startTimestamp', 'page', 'endTimestamp'------ <http://www.last.fm/api/show/user.getArtistTracks>-getArtistTracks :: Request f (User -> Artist -> APIKey -> Ready)-getArtistTracks = api "user.getArtistTracks"-{-# INLINE getArtistTracks #-}----- | Returns the tracks banned by the user------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/user.getBannedTracks>-getBannedTracks :: Request f (User -> APIKey -> Ready)-getBannedTracks = api "user.getBannedTracks"-{-# INLINE getBannedTracks #-}----- | Get a list of upcoming events that this user is attending.--- Easily integratable into calendars, using the ical standard (see 'more formats' section below).------ Optional: 'page', 'festivalsonly', 'limit'------ <http://www.last.fm/api/show/user.getEvents>-getEvents :: Request f (User -> APIKey -> Ready)-getEvents = api "user.getEvents"-{-# INLINE getEvents #-}----- | Get a list of the user's friends on Last.fm.------ Optional: 'recenttracks', 'limit', 'page'------ <http://www.last.fm/api/show/user.getFriends>-getFriends :: Request f (User -> APIKey -> Ready)-getFriends = api "user.getFriends"-{-# INLINE getFriends #-}----- | Get information about a user profile.------ <http://www.last.fm/api/show/user.getInfo>-getInfo :: Request f (User -> APIKey -> Ready)-getInfo = api "user.getInfo"-{-# INLINE getInfo #-}----- | Get the last 50 tracks loved by a user.------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/user.getLovedTracks>-getLovedTracks :: Request f (User -> APIKey -> Ready)-getLovedTracks = api "user.getLovedTracks"-{-# INLINE getLovedTracks #-}----- | Get a list of a user's neighbours on Last.fm.------ Optional: 'limit'------ <http://www.last.fm/api/show/user.getNeighbours>-getNeighbours :: Request f (User -> APIKey -> Ready)-getNeighbours = api "user.getNeighbours"-{-# INLINE getNeighbours #-}----- | Gets a list of forthcoming releases based on a user's musical taste.------ Optional: 'userecs'------ <http://www.last.fm/api/show/user.getNewReleases>-getNewReleases :: Request f (User -> APIKey -> Ready)-getNewReleases = api "user.getNewReleases"-{-# INLINE getNewReleases #-}----- | Get a paginated list of all events a user has attended in the past.------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/user.getPastEvents>-getPastEvents :: Request f (User -> APIKey -> Ready)-getPastEvents = api "user.getPastEvents"-{-# INLINE getPastEvents #-}----- | Get the user's personal tags------ Optional: 'taggingtype', 'limit', 'page'------ <http://www.last.fm/api/show/user.getPersonalTags>-getPersonalTags :: Request f (User -> Tag -> TaggingType -> APIKey -> Ready)-getPersonalTags = api "user.getPersonalTags"-{-# INLINE getPersonalTags #-}----- | Get a list of a user's playlists on Last.fm.------ <http://www.last.fm/api/show/user.getPlaylists>-getPlaylists :: Request f (User -> APIKey -> Ready)-getPlaylists = api "user.getPlaylists"-{-# INLINE getPlaylists #-}----- | Get a list of the recent Stations listened to by this user.------ Optional: 'limit', 'page'------ <http://www.last.fm/api/show/user.getRecentStations>-getRecentStations :: Request f (User -> APIKey -> SessionKey -> Sign)-getRecentStations = api "user.getRecentStations"-{-# INLINE getRecentStations #-}----- | Get a list of the recent tracks listened to by this user.--- Also includes the currently playing track with the nowplaying="true"--- attribute if the user is currently listening.------ Optional: 'limit', 'page', 'from', 'extended', 'to'------ <http://www.last.fm/api/show/user.getRecentTracks>-getRecentTracks :: Request f (User -> APIKey -> Ready)-getRecentTracks = api "user.getRecentTracks"-{-# INLINE getRecentTracks #-}----- | Get Last.fm artist recommendations for a user------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/user.getRecommendedArtists>-getRecommendedArtists :: Request f (APIKey -> SessionKey -> Sign)-getRecommendedArtists = api "user.getRecommendedArtists"-{-# INLINE getRecommendedArtists #-}----- | Get a paginated list of all events recommended to a user by Last.fm, based on their listening profile.------ Optional: 'limit', 'page', 'latitude', 'longitude', 'festivalsonly', 'country'------ <http://www.last.fm/api/show/user.getRecommendedEvents>-getRecommendedEvents :: Request f (APIKey -> SessionKey -> Sign)-getRecommendedEvents = api "user.getRecommendedEvents"-{-# INLINE getRecommendedEvents #-}----- | Get shouts for this user. Also available as an rss feed.------ Optional: 'page', 'limit'------ <http://www.last.fm/api/show/user.getShouts>-getShouts :: Request f (User -> APIKey -> Ready)-getShouts = api "user.getShouts"-{-# INLINE getShouts #-}----- | Get the top albums listened to by a user.--- You can stipulate a time period. Sends the overall chart by default.------ Optional: 'period', 'limit', 'page'------ <http://www.last.fm/api/show/user.getTopAlbums>-getTopAlbums :: Request f (User -> APIKey -> Ready)-getTopAlbums = api "user.getTopAlbums"-{-# INLINE getTopAlbums #-}----- | Get the top artists listened to by a user.--- You can stipulate a time period. Sends the overall chart by default.------ Optional: 'period', 'limit', 'page'------ <http://www.last.fm/api/show/user.getTopArtists>-getTopArtists :: Request f (User -> APIKey -> Ready)-getTopArtists = api "user.getTopArtists"-{-# INLINE getTopArtists #-}----- | Get the top tags used by this user.------ Optional: 'limit'------ <http://www.last.fm/api/show/user.getTopTags>-getTopTags :: Request f (User -> APIKey -> Ready)-getTopTags = api "user.getTopTags"-{-# INLINE getTopTags #-}----- | Get the top tracks listened to by a user.--- You can stipulate a time period. Sends the overall chart by default.------ Optional: 'period', 'limit', 'page'------ <http://www.last.fm/api/show/user.getTopTracks>-getTopTracks :: Request f (User -> APIKey -> Ready)-getTopTracks = api "user.getTopTracks"-{-# INLINE getTopTracks #-}----- | Get an album chart for a user profile, for a given date range.--- If no date range is supplied, it will return the most recent album chart for this user.------ Optional: 'from', 'to'------ <http://www.last.fm/api/show/user.getWeeklyAlbumChart>-getWeeklyAlbumChart :: Request f (User -> APIKey -> Ready)-getWeeklyAlbumChart = api "user.getWeeklyAlbumChart"-{-# INLINE getWeeklyAlbumChart #-}----- | Get an artist chart for a user profile, for a given date range.--- If no date range is supplied, it will return the most recent artist chart for this user.------ Optional: 'from', 'to'------ <http://www.last.fm/api/show/user.getWeeklyArtistChart>-getWeeklyArtistChart :: Request f (User -> APIKey -> Ready)-getWeeklyArtistChart = api "user.getWeeklyArtistChart"-{-# INLINE getWeeklyArtistChart #-}----- | Get a list of available charts for this user, expressed as--- date ranges which can be sent to the chart services.------ <http://www.last.fm/api/show/user.getWeeklyChartList>-getWeeklyChartList :: Request f (User -> APIKey -> Ready)-getWeeklyChartList = api "user.getWeeklyChartList"-{-# INLINE getWeeklyChartList #-}----- | Get a track chart for a user profile, for a given date range.--- If no date range is supplied, it will return the most recent track chart for this user.------ Optional: 'from', 'to'------ <http://www.last.fm/api/show/user.getWeeklyTrackChart>-getWeeklyTrackChart :: Request f (User -> APIKey -> Ready)-getWeeklyTrackChart = api "user.getWeeklyTrackChart"-{-# INLINE getWeeklyTrackChart #-}----- | Shout on this user's shoutbox------ <http://www.last.fm/api/show/user.shout>-shout :: Request f (User -> Message -> APIKey -> SessionKey -> Sign)-shout = api "user.shout" <* post-{-# INLINE shout #-}
− src/Network/Lastfm/Venue.hs
@@ -1,41 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}--- | Lastfm venue API------ This module is intended to be imported qualified:------ @--- import qualified Network.Lastfm.Venue as Venue--- @-module Network.Lastfm.Venue- ( getEvents, getPastEvents, search- ) where--import Network.Lastfm.Request----- | Get a list of upcoming events at this venue.------ Optional: 'festivalsonly'------ <http://www.last.fm/api/show/venue.getEvents>-getEvents :: Request f (Venue -> APIKey -> Ready)-getEvents = api "venue.getEvents"----- | Get a paginated list of all the events held at this venue in the past.------ Optional: 'festivalsonly', 'page', 'limit'------ <http://www.last.fm/api/show/venue.getPastEvents>-getPastEvents :: Request f (Venue -> APIKey -> Ready)-getPastEvents = api "venue.getPastEvents"----- | Search for a venue by venue name------ Optional: 'page', 'limit', 'country'------ <http://www.last.fm/api/show/venue.search>-search :: Request f (VenueName -> APIKey -> Ready)-search = api "venue.search"
test/api/Json/AlbumSpec.hs view
@@ -5,8 +5,8 @@ import Data.Aeson (Value) import Data.Aeson.Lens import Data.Text (Text)-import Network.Lastfm-import Network.Lastfm.Album+import Lastfm+import Lastfm.Album import Test.Hspec import SpecHelper
test/api/Json/ArtistSpec.hs view
@@ -5,8 +5,8 @@ import Data.Aeson (Value) import Data.Aeson.Lens import Data.Text (Text)-import Network.Lastfm-import Network.Lastfm.Artist+import Lastfm+import Lastfm.Artist import Test.Hspec import SpecHelper
test/api/Json/ChartSpec.hs view
@@ -5,8 +5,8 @@ import Data.Aeson (Value) import Data.Aeson.Lens import Data.Text (Text)-import Network.Lastfm-import Network.Lastfm.Chart+import Lastfm+import Lastfm.Chart import Test.Hspec import SpecHelper
test/api/Json/EventSpec.hs view
@@ -3,8 +3,8 @@ module Json.EventSpec (spec) where import Data.Aeson.Lens-import Network.Lastfm-import Network.Lastfm.Event+import Lastfm+import Lastfm.Event import Test.Hspec import SpecHelper
test/api/Json/GeoSpec.hs view
@@ -5,8 +5,8 @@ import Data.Aeson (Value) import Data.Aeson.Lens import Data.Text (Text)-import Network.Lastfm-import Network.Lastfm.Geo+import Lastfm+import Lastfm.Geo import Test.Hspec import SpecHelper
test/api/Json/GroupSpec.hs view
@@ -3,8 +3,8 @@ module Json.GroupSpec (spec) where import Data.Aeson.Lens-import Network.Lastfm-import Network.Lastfm.Group+import Lastfm+import Lastfm.Group import Test.Hspec import SpecHelper
test/api/Json/LibrarySpec.hs view
@@ -3,8 +3,8 @@ module Json.LibrarySpec (spec) where import Data.Aeson.Lens-import Network.Lastfm-import Network.Lastfm.Library+import Lastfm+import Lastfm.Library import Test.Hspec import SpecHelper
test/api/Json/PlaylistSpec.hs view
@@ -7,9 +7,9 @@ import Data.Aeson.Lens import Data.Int (Int64) import Data.Text.Lens (unpacked)-import Network.Lastfm hiding (to)-import Network.Lastfm.Playlist-import Network.Lastfm.User+import Lastfm hiding (to)+import Lastfm.Playlist+import Lastfm.User import Test.Hspec import Test.HUnit (assertFailure) import Text.Printf
test/api/Json/TagSpec.hs view
@@ -3,8 +3,8 @@ module Json.TagSpec (spec) where import Data.Aeson.Lens-import Network.Lastfm-import Network.Lastfm.Tag+import Lastfm+import Lastfm.Tag import Test.Hspec import SpecHelper
test/api/Json/TasteometerSpec.hs view
@@ -5,8 +5,8 @@ import Data.Aeson (Value) import Data.Aeson.Lens import Data.Text (Text)-import Network.Lastfm-import qualified Network.Lastfm.Tasteometer as Taste+import Lastfm+import qualified Lastfm.Tasteometer as Taste import Test.Hspec import SpecHelper
test/api/Json/TrackSpec.hs view
@@ -5,8 +5,8 @@ import Data.Aeson (Value) import Data.Aeson.Lens import Data.Text (Text)-import Network.Lastfm-import Network.Lastfm.Track+import Lastfm+import Lastfm.Track import Test.Hspec import SpecHelper
test/api/Json/UserSpec.hs view
@@ -3,8 +3,8 @@ module Json.UserSpec (spec) where import Data.Aeson.Lens-import Network.Lastfm-import Network.Lastfm.User+import Lastfm+import Lastfm.User import Test.Hspec import SpecHelper
test/api/Json/VenueSpec.hs view
@@ -3,8 +3,8 @@ module Json.VenueSpec (spec) where import Data.Aeson.Lens-import Network.Lastfm-import Network.Lastfm.Venue+import Lastfm+import Lastfm.Venue import Test.Hspec import SpecHelper
test/api/SpecHelper.hs view
@@ -33,7 +33,7 @@ import Data.Typeable (Typeable) import qualified Network.HTTP.Client as Http import qualified Network.HTTP.Client.TLS as Http-import Network.Lastfm+import Lastfm import System.Environment import System.IO.Unsafe (unsafePerformIO) import Test.Hspec@@ -53,17 +53,17 @@ Just _ -> return () Nothing -> assertFailure (printf "Query failed on %s" (show r)) -shouldHaveJson :: Request JSON Ready -> Fold Value a -> Expectation+shouldHaveJson :: Request 'JSON Ready -> Fold Value a -> Expectation shouldHaveJson = shouldHaveResponse -shouldHaveXml :: Request XML Ready -> Fold Document a -> Expectation+shouldHaveXml :: Request 'XML Ready -> Fold Document a -> Expectation shouldHaveXml = shouldHaveResponse -- | Check success stuff for POST requests-shouldHaveJson_ :: Request JSON Ready -> Expectation+shouldHaveJson_ :: Request 'JSON Ready -> Expectation shouldHaveJson_ l = shouldHaveResponse l (key "status".only "ok") -shouldHaveXml_ :: Request XML Ready -> Expectation+shouldHaveXml_ :: Request 'XML Ready -> Expectation shouldHaveXml_ l = shouldHaveResponse l (root.attributed (ix "status".only "ok")) -- | Make a request using public API key
test/api/Xml/AlbumSpec.hs view
@@ -3,8 +3,8 @@ module Xml.AlbumSpec (spec) where import Data.Text (Text)-import Network.Lastfm-import Network.Lastfm.Album+import Lastfm+import Lastfm.Album import Test.Hspec import Text.Xml.Lens
test/api/Xml/ArtistSpec.hs view
@@ -4,8 +4,8 @@ import Data.Text (Text) import Data.Traversable (traverse)-import Network.Lastfm-import Network.Lastfm.Artist+import Lastfm+import Lastfm.Artist import Test.Hspec import Text.Xml.Lens
test/api/Xml/ChartSpec.hs view
@@ -3,8 +3,8 @@ module Xml.ChartSpec (spec) where import Data.Text (Text)-import Network.Lastfm-import Network.Lastfm.Chart+import Lastfm+import Lastfm.Chart import Test.Hspec import Text.Xml.Lens
test/api/Xml/EventSpec.hs view
@@ -2,8 +2,8 @@ {-# LANGUAGE OverloadedStrings #-} module Xml.EventSpec (spec) where -import Network.Lastfm-import Network.Lastfm.Event+import Lastfm+import Lastfm.Event import Test.Hspec import Text.Xml.Lens
test/api/Xml/GeoSpec.hs view
@@ -3,8 +3,8 @@ module Xml.GeoSpec (spec) where import Data.Text (Text)-import Network.Lastfm-import Network.Lastfm.Geo+import Lastfm+import Lastfm.Geo import Test.Hspec import Text.Xml.Lens
test/api/Xml/GroupSpec.hs view
@@ -2,8 +2,8 @@ {-# LANGUAGE OverloadedStrings #-} module Xml.GroupSpec (spec) where -import Network.Lastfm-import Network.Lastfm.Group+import Lastfm+import Lastfm.Group import Test.Hspec import Text.Xml.Lens
test/api/Xml/LibrarySpec.hs view
@@ -2,8 +2,8 @@ {-# LANGUAGE OverloadedStrings #-} module Xml.LibrarySpec (spec) where -import Network.Lastfm-import Network.Lastfm.Library+import Lastfm+import Lastfm.Library import Test.Hspec import Text.Xml.Lens
test/api/Xml/PlaylistSpec.hs view
@@ -5,9 +5,9 @@ import Control.Lens import Data.Int (Int64) import Data.Text.Lens (unpacked)-import Network.Lastfm hiding (to)-import Network.Lastfm.Playlist-import Network.Lastfm.User+import Lastfm hiding (to)+import Lastfm.Playlist+import Lastfm.User import Test.Hspec import Test.HUnit (assertFailure) import Text.Printf@@ -25,7 +25,7 @@ root.node "playlists".node "playlist".node "title".text it "addTrack" $ do- r <- lastfm man $ getPlaylists <*> user "liblastfm" <*> ak' <* Network.Lastfm.xml+ r <- lastfm man $ getPlaylists <*> user "liblastfm" <*> ak' <* Lastfm.xml case r of Left e -> assertFailure (printf "last.fm error: %s" (show e))
test/api/Xml/TagSpec.hs view
@@ -2,8 +2,8 @@ {-# LANGUAGE OverloadedStrings #-} module Xml.TagSpec (spec) where -import Network.Lastfm-import Network.Lastfm.Tag+import Lastfm+import Lastfm.Tag import Test.Hspec import Text.Xml.Lens
test/api/Xml/TasteometerSpec.hs view
@@ -3,8 +3,8 @@ module Xml.TasteometerSpec (spec) where import Data.Text (Text)-import Network.Lastfm-import qualified Network.Lastfm.Tasteometer as Taste+import Lastfm+import qualified Lastfm.Tasteometer as Taste import Test.Hspec import Text.Xml.Lens
test/api/Xml/TrackSpec.hs view
@@ -4,8 +4,8 @@ import Data.Text (Text) import Data.Traversable (traverse)-import Network.Lastfm-import Network.Lastfm.Track+import Lastfm+import Lastfm.Track import Test.Hspec import Text.Xml.Lens
test/api/Xml/UserSpec.hs view
@@ -2,8 +2,8 @@ {-# LANGUAGE OverloadedStrings #-} module Xml.UserSpec (spec) where -import Network.Lastfm-import Network.Lastfm.User+import Lastfm+import Lastfm.User import Test.Hspec import Text.Xml.Lens
test/api/Xml/VenueSpec.hs view
@@ -2,8 +2,8 @@ {-# LANGUAGE OverloadedStrings #-} module Xml.VenueSpec (spec) where -import Network.Lastfm-import Network.Lastfm.Venue+import Lastfm+import Lastfm.Venue import Test.Hspec import Text.Xml.Lens
+ test/spec/Lastfm/ResponseSpec.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Lastfm.ResponseSpec (spec) where++import Control.Exception (ArithException(..), throwIO, try)+import Control.Exception.Lens+import Control.Lens+import Data.Aeson (Value)+import Data.ByteString.Lazy (ByteString)+import Data.Aeson.Lens+import Test.Hspec.Lens+import Text.Xml.Lens+import Network.HTTP.Client (HttpException(..))++import Lastfm.Response+++instance Eq HttpException where+ _ == _ = True+++spec :: Spec+spec = do+ describe "md5" $ do+ it "calculates the right hash for an empty string" $+ md5 "" `shouldBe` "d41d8cd98f00b204e9800998ecf8427e"++ it "calculates the right hash for an ascii string" $+ md5 "Enduser" `shouldBe` "28ced1fafec20ae302a04e9f27f4800f"++ it "calculates the right hash for a unicode string" $+ md5 "ДМИТРИЙ МАЛИКОВ" `shouldBe` "e02e5affef1004a02d9762619dc2a585"++ describe "try for LastfmError" $ do+ let tryLastfmError :: IO a -> IO (Either LastfmError a)+ tryLastfmError = try++ it "catches 'HttpException'" $ do+ val <- tryLastfmError (throwIO ResponseTimeout) :: IO (Either LastfmError ())+ val `shouldHave` _Left._LastfmHttpError.only ResponseTimeout++ it "does not catch other exceptions" $+ tryLastfmError (throwIO DivideByZero) `shouldThrow` _DivideByZero++ describe "parse" $ do+ context "JSON" $ do+ let parseJSON :: ByteString -> Either LastfmError Value+ parseJSON = parse++ it "handles good input" $+ let+ good = "{ \"a\": { \"b\": 4 } }"+ in+ parseJSON good `shouldHave` _Right.key "a".key "b"._Integer.only 4++ it "handles malformed input" $+ let+ malformed = "not a json"+ in+ parseJSON malformed `shouldHave` _Left._LastfmBadResponse.only malformed++ it "handles input with encoded errors" $+ let+ encodedError = "{ \"error\": 5, \"message\": \"foo\" }"+ in+ parseJSON encodedError `shouldHave` _Left._LastfmEncodedError.only (5, "foo")++ context "XML" $ do+ let parseXML :: ByteString -> Either LastfmError Document+ parseXML = parse++ it "handles good input" $+ let+ good = "<root><foo><bar>baz</bar></foo></root>"+ in+ parseXML good `shouldHave` _Right.root.node "foo".node "bar".text.only "baz"++ it "handles malformed input" $+ let+ malformed = "not a xml"+ in+ parseXML malformed `shouldHave` _Left._LastfmBadResponse.only malformed++ it "handles input with encoded errors" $+ let+ encodedError = "<lfm><error code=\"5\">foo</error></lfm>"+ in+ parseXML encodedError `shouldHave` _Left._LastfmEncodedError.only (5, "foo")
− test/spec/Network/Lastfm/ResponseSpec.hs
@@ -1,90 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-module Network.Lastfm.ResponseSpec (spec) where--import Control.Exception (ArithException(..), throwIO, try)-import Control.Exception.Lens-import Control.Lens-import Data.Aeson (Value)-import Data.ByteString.Lazy (ByteString)-import Data.Aeson.Lens-import Test.Hspec.Lens-import Text.Xml.Lens-import Network.HTTP.Client (HttpException(..))--import Network.Lastfm.Response---instance Eq HttpException where- _ == _ = True---spec :: Spec-spec = do- describe "md5" $ do- it "calculates the right hash for an empty string" $- md5 "" `shouldBe` "d41d8cd98f00b204e9800998ecf8427e"-- it "calculates the right hash for an ascii string" $- md5 "Enduser" `shouldBe` "28ced1fafec20ae302a04e9f27f4800f"-- it "calculates the right hash for a unicode string" $- md5 "ДМИТРИЙ МАЛИКОВ" `shouldBe` "e02e5affef1004a02d9762619dc2a585"-- describe "try for LastfmError" $ do- let tryLastfmError :: IO a -> IO (Either LastfmError a)- tryLastfmError = try-- it "catches 'HttpException'" $ do- val <- tryLastfmError (throwIO ResponseTimeout) :: IO (Either LastfmError ())- val `shouldHave` _Left._LastfmHttpError.only ResponseTimeout-- it "does not catch other exceptions" $- tryLastfmError (throwIO DivideByZero) `shouldThrow` _DivideByZero-- describe "parse" $ do- context "JSON" $ do- let parseJSON :: ByteString -> Either LastfmError Value- parseJSON = parse-- it "handles good input" $- let- good = "{ \"a\": { \"b\": 4 } }"- in- parseJSON good `shouldHave` _Right.key "a".key "b"._Integer.only 4-- it "handles malformed input" $- let- malformed = "not a json"- in- parseJSON malformed `shouldHave` _Left._LastfmBadResponse.only malformed-- it "handles input with encoded errors" $- let- encodedError = "{ \"error\": 5, \"message\": \"foo\" }"- in- parseJSON encodedError `shouldHave` _Left._LastfmEncodedError.only (5, "foo")-- context "XML" $ do- let parseXML :: ByteString -> Either LastfmError Document- parseXML = parse-- it "handles good input" $- let- good = "<root><foo><bar>baz</bar></foo></root>"- in- parseXML good `shouldHave` _Right.root.node "foo".node "bar".text.only "baz"-- it "handles malformed input" $- let- malformed = "not a xml"- in- parseXML malformed `shouldHave` _Left._LastfmBadResponse.only malformed-- it "handles input with encoded errors" $- let- encodedError = "<lfm><error code=\"5\">foo</error></lfm>"- in- parseXML encodedError `shouldHave` _Left._LastfmEncodedError.only (5, "foo")