packages feed

liblastfm 0.4.1.0 → 0.5.0

raw patch · 27 files changed

+734/−678 lines, 27 filesdep ~http-clientdep ~http-client-tlsPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: http-client, http-client-tls

API changes (from Hackage documentation)

- Network.Lastfm.Response: finalize :: Supported f r => Request f Ready -> R f
- Network.Lastfm.Response: lastfmWith :: Supported f r => (ByteString -> Either LastfmError a) -> R f -> IO (Either LastfmError a)
+ Network.Lastfm.Response: data Connection
+ Network.Lastfm.Response: withConnection :: (Connection -> IO a) -> IO a
- Network.Lastfm.Response: lastfm :: Supported f r => Request f Ready -> IO (Either LastfmError r)
+ Network.Lastfm.Response: lastfm :: Supported f r => Connection -> Request f Ready -> IO (Either LastfmError r)
- Network.Lastfm.Response: lastfm_ :: Supported f r => Request f Ready -> IO (Either LastfmError ())
+ Network.Lastfm.Response: lastfm_ :: Supported f r => Connection -> Request f Ready -> IO (Either LastfmError ())

Files

+ CHANGELOG.markdown view
@@ -0,0 +1,36 @@+0.5.0+=====++  * Fixed `http-client`'s `Manager` misuse: every call to `lastfm*` takes a+    `Connection` parameter now and multiple calls should share it.++0.4.1.0+=======++  * Updated `lens` dependency++  * Switched to `network-uri` package++0.4.0.0+=======++  * `http-conduit` exceptions aren't propagate further into user code anymore++  * XML responses are parsed into `Document` type from `xml-conduit`++0.3.2.0+=======++  * Fixed `auth.getMobileSession` API method++  * Provided `lastfm_` function for making requests without parsing response. That is most useful for `POST` requests like `N.L.Track.love`++0.3.0.0+=======++  * Supported batch operations for `N.L.Track.scrobble`, `N.L.Library.{addAlbum,addArtist}`++0.2.0.0+=======++  * Vastly simplified internal representations leading to simpler library interface
− CHANGELOG.md
@@ -1,30 +0,0 @@-0.4.1.0-=======--  * Updated `lens` dependency--  * Switched to `network-uri` package--0.4.0.0-=======--  * `http-conduit` exceptions aren't propagate further into user code anymore--  * XML responses are parsed into `Document` type from `xml-conduit`--0.3.2.0-=======--  * Fixed `auth.getMobileSession` API method--  * Provided `lastfm_` function for making requests without parsing response. That is most useful for `POST` requests like `N.L.Track.love`--0.3.0.0-=======--  * Supported batch operations for `N.L.Track.scrobble`, `N.L.Library.{addAlbum,addArtist}`--0.2.0.0-=======--  * Vastly simplified internal representations leading to simpler library interface
+ README.markdown view
@@ -0,0 +1,127 @@+liblastfm+=========+[![Hackage](https://budueba.com/hackage/liblastfm)](https://hackage.haskell.org/package/liblastfm)+[![Build Status](https://secure.travis-ci.org/supki/liblastfm.png?branch=master)](https://travis-ci.org/supki/liblastfm)++Complete API interface to [last.fm][last.fm] service.+Documentation is available in two flavours:++  * original [API reference][last.fm/api]++  * liblastfm [haddocks][liblastfm/haddocks]++Introduction+------------+liblastfm provides Applicative interface for constructing requests. Also, it handles all machinery needed to prepare request for sending:++  * [signing][last.fm/sign]++  * url-encoding++  * miscellaneous stuff like choosing correct HTTP method, etc++Once request is ready, liblastfm can send it and get you back a response.+Response format might be:++  * [aeson][aeson] `Value` for json queries++  * [xml-conduit][xml-conduit] `Document` for xml queries++Installation+------------+To install either use hackage:++    % cabal install liblastfm++Or git:++    % git clone git@github.com:supki/liblastfm+    % cd liblastfm+    % cabal install++Usage+-----+Suppose, you need to use [`tag.search`][last.fm/api-usage] API method.+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++Now you may you applicative `<*>` for required and `<*` or `*>` for optional parameters to construct+desired request:++    Tag.search <*> tag "russian-folk" <* limit 3 <*> apiKey "29effec263316a1f8a97f753caaa83e0" <* json++To send constructed request use `lastfm`:++    >>> lastfm $ Tag.search <*> tag "russian-folk" <* limit 10 <*> apiKey "29effec263316a1f8a97f753caaa83e0" <* json+    Just (Object fromList [("results",Object fromList [("tagmatches", ...++[Wiki][liblastfm/wiki] describes how to parse responses.++FAQ+---++**Q: I'm getting the following error. How do I fix it?**++```+>>> Artist.getInfo <*> artist "Pink Floyd" <*> apiKey "29effec263316a1f8a97f753caaa83e0"++<interactive>:8:27:+    Couldn't match expected type `Data.Text.Lazy.Internal.Text'+                with actual type `[Char]+```++A: This means you haven't OverloadedStrings extension enabled.+To enable it (either one works):++  * type in `:set -XOverloadedStrings` while in ghci session.++  * add `{-# LANGUAGE OverloadedStrings #-}` to the top of the file++  * compile with `-XOverloadedStrings` switch++**Q: I'm getting the following error. How do I fix it?**++```+>>> lastfm (Artist.getInfo <*> artist "Pink Floyd" <*> apiKey "29effec263316a1f8a97f753caaa83e0")++<interactive>:13:1:+    No instance for (Show (IO (Either LastfmError r0)))+      arising from a use of `print'+    Possible fix:+      add an instance declaration for (Show (IO (Either LastfmError r0)))+    In the first argument of `print', namely `it'+    In a stmt of an interactive GHCi command: print it+```++A: This error message indicates that GHC cannot infer response format for the `Request`.+To fix it, add use `json` or `xml` helpers, depending on your needs++A note on testing+-----------------++To test Lastfm API compatibility (`api` test suite)—specifically, authentication requiring+examples—you will need to set `HASKELL_LIBLASTFM_APIKEY`, `HASKELL_LIBLASTFM_SESSIONKEY`,+and `HASKELL_LIBLASTFM_SECRET` environment variables to your api key, session key, and+secret respectively; for example (bash):++```bash+export HASKELL_LIBLASTFM_APIKEY="__API_KEY__"+export HASKELL_LIBLASTFM_SESSIONKEY="__SESSION_KEY__"+export HASKELL_LIBLASTFM_SECRET="__SECRET__"+```++Please, consult Lastfm API documentation and  `examples/*-authentication.hs`+examples if you don't know where to get your credentials.++ [last.fm]: http://www.last.fm/+ [last.fm/api]: http://www.last.fm/api/intro+ [last.fm/api-usage]: http://www.last.fm/api/show/tag.search+ [last.fm/sign]: http://www.last.fm/api/authspec#8+ [liblastfm/haddocks]: http://supki.github.io/liblastfm/+ [liblastfm/haddocks-usage]: http://supki.github.com/liblastfm/Network-Lastfm-Tag.html#v:search+ [liblastfm/wiki]: https://github.com/supki/liblastfm/wiki/How-to-parse-JSON-response+ [aeson]: https://hackage.haskell.org/package/aeson+ [xml-conduit]: https://hackage.haskell.org/package/xml-conduit
− README.md
@@ -1,127 +0,0 @@-liblastfm-=========-[![Hackage](https://budueba.com/hackage/liblastfm)](https://hackage.haskell.org/package/liblastfm)-[![Build Status](https://secure.travis-ci.org/supki/liblastfm.png?branch=develop)](https://travis-ci.org/supki/liblastfm)--Complete API interface to [last.fm][last.fm] service.-Documentation is available in two flavours:--  * original [API reference][last.fm/api]--  * liblastfm [haddocks][liblastfm/haddocks]--Introduction--------------liblastfm provides Applicative interface for constructing requests. Also, it handles all machinery needed to prepare request for sending:--  * [signing][last.fm/sign]--  * url-encoding--  * miscellaneous stuff like choosing correct HTTP method, etc--Once request is ready, liblastfm can send it and get you back a response.-Response format might be:--  * [aeson][aeson] `Value` for json queries--  * [xml-conduit][xml-conduit] `Document` for xml queries--Installation--------------To install either use hackage:--    % cabal install liblastfm--Or git:--    % git clone git@github.com:supki/liblastfm-    % cd liblastfm-    % cabal install--Usage-------Suppose, you need to use [`tag.search`][last.fm/api-usage] API method.-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--Now you may you applicative `<*>` for required and `<*` or `*>` for optional parameters to construct-desired request:--    Tag.search <*> tag "russian-folk" <* limit 3 <*> apiKey "29effec263316a1f8a97f753caaa83e0" <* json--To send constructed request use `lastfm`:--    >>> lastfm $ Tag.search <*> tag "russian-folk" <* limit 10 <*> apiKey "29effec263316a1f8a97f753caaa83e0" <* json-    Just (Object fromList [("results",Object fromList [("tagmatches", ...--[Wiki][liblastfm/wiki] describes how to parse responses.--FAQ------**Q: I'm getting the following error. How do I fix it?**--```->>> Artist.getInfo <*> artist "Pink Floyd" <*> apiKey "29effec263316a1f8a97f753caaa83e0"--<interactive>:8:27:-    Couldn't match expected type `Data.Text.Lazy.Internal.Text'-                with actual type `[Char]-```--A: This means you haven't OverloadedStrings extension enabled.-To enable it (either one works):--  * type in `:set -XOverloadedStrings` while in ghci session.--  * add `{-# LANGUAGE OverloadedStrings #-}` to the top of the file--  * compile with `-XOverloadedStrings` switch--**Q: I'm getting the following error. How do I fix it?**--```->>> lastfm (Artist.getInfo <*> artist "Pink Floyd" <*> apiKey "29effec263316a1f8a97f753caaa83e0")--<interactive>:13:1:-    No instance for (Show (IO (Either LastfmError r0)))-      arising from a use of `print'-    Possible fix:-      add an instance declaration for (Show (IO (Either LastfmError r0)))-    In the first argument of `print', namely `it'-    In a stmt of an interactive GHCi command: print it-```--A: This error message indicates that GHC cannot infer response format for the `Request`.-To fix it, add use `json` or `xml` helpers, depending on your needs--A note on testing--------------------To test Lastfm API compatibility (`api` test suite)—specifically, authentication requiring-examples—you will need to set `HASKELL_LIBLASTFM_APIKEY`, `HASKELL_LIBLASTFM_SESSIONKEY`,-and `HASKELL_LIBLASTFM_SECRET` environment variables to your api key, session key, and-secret respectively; for example (bash):--```bash-export HASKELL_LIBLASTFM_APIKEY="__API_KEY__"-export HASKELL_LIBLASTFM_SESSIONKEY="__SESSION_KEY__"-export HASKELL_LIBLASTFM_SECRET="__SECRET__"-```--Please, consult Lastfm API documentation and  `examples/*-authentication.hs`-examples if you don't know where to get your credentials.-- [last.fm]: http://www.last.fm/- [last.fm/api]: http://www.last.fm/api/intro- [last.fm/api-usage]: http://www.last.fm/api/show/tag.search- [last.fm/sign]: http://www.last.fm/api/authspec#8- [liblastfm/haddocks]: http://supki.github.io/liblastfm/- [liblastfm/haddocks-usage]: http://supki.github.com/liblastfm/Network-Lastfm-Tag.html#v:search- [liblastfm/wiki]: https://github.com/supki/liblastfm/wiki/How-to-parse-JSON-response- [aeson]: https://hackage.haskell.org/package/aeson- [xml-conduit]: https://hackage.haskell.org/package/xml-conduit
+ example/README.markdown view
@@ -0,0 +1,82 @@+# liblastfm examples++## sort-friends++Shows how to work with lastfm api and async to speed up getting 5 most compliable friends.++  * [async][async]++  * [lens-aeson][lens-aeson]++---++## multitag-search++Shows how to work lastfm api around to get search for multiple tags at once.++  * [aeson][aeson]++---++## playcount++Shows how to get user playcount.++  * [aeson][aeson]++  * [lens-aeson][lens-aeson]++---++## recommendations++Shows how to use signed queries++  * [lens-aeson][lens-aeson]++---++## desktop-authentication++Demonstrates [desktop application authentication][last.fm/desktop] flow.++  * [lens-aeson][lens-aeson]++---++## web-authentication++Demonstrates [web application authentication][last.fm/web] flow.++  * [happstack-server][happstack-server]++---++## mobile-authentication++Demonstrates [mobile application authentication][last.fm/mobile] flow.++  * [lens-aeson][lens-aeson]++---++## [scrobblers][scrobblers]++Advanced scrobbling techniques.++  * [netwire][netwire]++  * [libmpd][libmpd]++  * [lens-aeson][lens-aeson]++ [aeson]: https://hackage.haskell.org/package/aeson+ [async]: https://hackage.haskell.org/package/async+ [happstack-server]: https://hackage.haskell.org/package/happstack-server+ [lens-aeson]: https://hackage.haskell.org/package/lens-aeson+ [libmpd]: https://hackage.haskell.org/package/libmpd+ [netwire]: https://hackage.haskell.org/package/netwire+ [scrobblers]: https://github.com/supki/scrobblers+ [last.fm/desktop]: http://www.last.fm/api/desktopauth+ [last.fm/web]: http://www.last.fm/api/webauth+ [last.fm/mobile]: http://www.last.fm/api/mobileauth
+ example/desktop-authentication.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Desktop application authentication flow example+--+-- 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+++main :: IO ()+main = withConnection $ \conn -> do+  r <- lastfm conn $ getToken <*> apiKey ak <* json+  for_ (r ^? folded.key "token"._String) $ \t -> do+    putStrLn $ "approve: " ++ link (apiKey ak <* token t)+    _ <- getChar+    r' <- lastfm conn . sign s $ getSession <*> token t <*> apiKey ak <* json+    for_ (r' ^? folded.key "session".key "key"._String) $ \sk ->+      putStrLn $ "session key: " ++ unpack sk+ where+  ak = "__YOUR_API_KEY__"+  s  = "__YOUR_SECRET__"
+ example/liblastfm-examples.cabal view
@@ -0,0 +1,120 @@+name:          liblastfm-examples+version:       0.5.0+synopsis:      Liblastfm examples+license:       MIT+license-file:  LICENSE+author:        Matvey Aksenov, Dmitry Malikov+maintainer:    Matvey Aksenov <matvey.aksenov@gmail.com>+category:      Network APIs+description:   Haddock documentation isn't enough to get feeling of the library+cabal-version: >= 1.10+build-type:    Simple++executable sort-friends+  default-language:+    Haskell2010+  build-depends:+      base             >= 4 && < 5+    , liblastfm        >= 0.4.0.0+    , async+    , text+    , aeson+    , lens             >= 4.4+    , lens-aeson       >= 1.0.0.1+  main-is:+    sort-friends.hs+  ghc-options:+    -Wall+    -fno-warn-unused-do-bind+    -threaded++executable multitag-search+  default-language:+    Haskell2010+  build-depends:+      base             >= 4 && < 5+    , liblastfm        >= 0.4.0.0+    , text+    , lens             >= 4.4+    , aeson+  main-is:+    multitag-search.hs+  ghc-options:+    -Wall+    -fno-warn-unused-do-bind++executable web-authentication+  default-language:+    Haskell2010+  build-depends:+      base             >= 4 && < 5+    , liblastfm        >= 0.4.0.0+    , lens             >= 4.4+    , lens-aeson       >= 1.0.0.1+    , happstack-server >= 7.3+    , transformers+  main-is:+    web-authentication.hs+  ghc-options:+    -Wall+    -fno-warn-unused-do-bind++executable desktop-authentication+  default-language:+    Haskell2010+  build-depends:+      base             >= 4 && < 5+    , liblastfm        >= 0.4.0.0+    , text+    , lens             >= 4.4+    , lens-aeson       >= 1.0.0.1+  main-is:+    desktop-authentication.hs+  ghc-options:+    -Wall+    -fno-warn-unused-do-bind++executable mobile-authentication+  default-language:+    Haskell2010+  build-depends:+      base             >= 4 && < 5+    , liblastfm        >= 0.4.0.0+    , lens             >= 4.4+    , lens-aeson       >= 1.0.0.1+    , text+  main-is:+    mobile-authentication.hs+  ghc-options:+    -Wall++executable playcount+  default-language:+    Haskell2010+  build-depends:+      base             >= 4 && < 5+    , liblastfm        >= 0.4.0.0+    , text+    , lens             >= 4.4+    , lens-aeson       >= 1.0.0.1+  main-is:+    playcount.hs+  ghc-options:+    -Wall+    -fno-warn-unused-do-bind++executable recommendations+  default-language:+    Haskell2010+  build-depends:+      base             >= 4 && < 5+    , liblastfm        >= 0.4.0.0+    , text+    , aeson+    , lens             >= 4.4+    , lens-aeson       >= 1.0.0.1+  main-is:+    recommendations.hs+  ghc-options:+    -Wall+    -fno-warn-unused-do-bind
+ example/mobile-authentication.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Mobile application authentication flow example+--+-- 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++main :: IO ()+main = withConnection $ \conn -> do+  r <- lastfm conn . sign s $ getMobileSession <*> username u <*> password p <*> apiKey ak <* json+  let maybeSk = r ^? folded.key "session".key "key"._String+  Text.putStrLn $ case maybeSk of+    Just sk -> "Mobile session key: " `Text.append` sk+    Nothing -> "Mobile session key wasn't retrieved, something goes wrong"+ where+  ak = "__YOUR_API_KEY__"+  s  = "__YOUR_SECRET__"+  u  = "__USERNAME__"+  p  = "__PASSWORD__"
+ example/multitag-search.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE OverloadedStrings #-}+{- This example shows how to do multitag search even+ - if it's not provided by Lastfm API+ -+ - Sample output:+ -+ - $> ./examples/multitag-search.hs j-pop anime anime-ost+ - 坂本真綾+ - KOTOKO+ - Lisa+ - 茅原実里+ -}+import           Data.Aeson.Types             -- aeson+import           Data.List (intersect)        -- base+import           Data.Maybe                   -- base+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           System.Environment (getArgs) -- base++{-# ANN module ("HLint: ignore Use camelCase" :: String) #-}+++main :: IO ()+main = mapM_ Text.putStrLn =<< withConnection . getArtists . map Text.pack =<< getArgs+++getArtists :: [Text] -> Connection -> IO [Text]+getArtists ts conn = do+  names <- mapM request ts+  case catMaybes names of+    [] -> return []+    ns -> return (foldl1 intersect ns)+ where+  request = fmap (either (const Nothing) (parseMaybe gta)) . query++  query t = lastfm conn $+    Tag.getTopArtists <*> tag t <* limit 100 <*> apiKey "29effec263316a1f8a97f753caaa83e0" <* json++gta :: Value -> Parser [Text]+gta o = parseJSON o >>= (.: "topartists") >>= (.: "artist") >>= mapM (.: "name")
+ example/playcount.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE OverloadedStrings #-}+-- This example shows how to get user playcount with lens-aeson+--+-- Sample session:+--+-- $> ./examples/playcount.hs+-- >>> MCDOOMDESTROYER+-- 96698+-- >>> smpcln+-- 95518+-- >>> ^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+++main :: IO ()+main = withConnection $ \conn ->+  forever $ do Text.putStr ">>> "; flush; Text.getLine >>= playcount conn >>= Text.putStrLn++flush :: IO ()+flush = IO.hFlush IO.stdout++playcount :: Connection -> Text -> IO Text+playcount conn u = do+  res <- lastfm conn+    (User.getInfo <*> user u <*> apiKey "29effec263316a1f8a97f753caaa83e0" <* json)+  return $ case res ^? folded.key "user".key "playcount"._String of+    Nothing -> "Playcount not found"+    Just x  -> x+{-# ANN playcount ("HLint: ignore Use fromMaybe" :: String) #-}
+ example/recommendations.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{- This example shows how to use signed queries+ - to get user recommendations+ -+ - Sample output:+ -+ - Roger Rotor+ - Neurosis+ - Asche+ - SKYHARBOR+ - Cylob+ - 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+++main :: IO ()+main = withConnection $ \conn -> do+  r <- for [1..pages] $ \p -> parse `fmap` query conn (User.getRecommendedArtists <* page p)+  mapM_ Text.putStrLn (concat r)++-- Construct signed query+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__"+  sk     = sessionKey "__YOUR_SESSION_KEY__"+  secret = "__YOUR_SECRET__"++-- Other query parameters+total, pages :: Int64+total = 250+pages = total `div` 50 -- 50 is the default number of recommendations per page++-- Parse artist names from response+parse :: Foldable f => f Value -> [Text]+parse x = x ^.. folded.key "recommendations".key "artist".values.key "name"._String
+ example/sort-friends.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{- This example shows how to extract all+ - user friends, get their similarity score and+ - then sort them based on it outputting top 5+ -+ - User friends of which are searched is 'target'+ -+ - Most of hard work there is on 'aeson-lens' actually :)+ -+ - Sample output:+ -+ - % ./examples/sort-friends.hs+ - Artvart: 0.9969767332077+ - smpcln: 0.9965825676918+ - nv0id: 0.86804795265198+ - QueenrXy: 0.5932799577713+ - GhostOsaka: 0.2875879406929+ -+ - 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+++type Score = Text+++main :: IO ()+main = withConnection $ \conn ->+  pages conn >>= scores conn . names >>= pretty+ where+  names x = x ^.. folded.folded.key "friends".key "user"._Array.folded.key "name"._String++pages :: Connection -> IO [Either LastfmError Value]+pages conn = do+  first <- query conn (User.getFriends <*> user target)+  let ps = fromMaybe 1 (preview total first)+  (first :) <$> forConcurrently [2..ps] (\p -> query conn (User.getFriends <*> user target <* page p))+ where+  total = folded.key "friends".key "@attr".key "totalPages"._String.unpacked.to readMaybe.folded++scores :: Connection -> [Text] -> IO [(Text, Score)]+scores conn xs = zip xs <$> forConcurrently xs (\x -> do+  r <- query conn (Tasteometer.compare (user target) (user x))+  return (fromMaybe "0" (preview score r)))+ where+  score = folded.key "comparison".key "result".key "score"._String++forConcurrently :: [a] -> (a -> IO b) -> IO [b]+forConcurrently = flip mapConcurrently++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 conn r = lastfm conn (r <*> apiKey "234fc6e0f41f6ef99b7bd62ebaf8d318" <* json)++target :: Text+target = "MCDOOMDESTROYER"
+ example/web-authentication.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Web application authentication flow example+--+-- Please remember to substitute __YOUR_API_KEY__,+-- __YOUR_SECRET__ and __YOUR_CALLBACK__ for real values+import Control.Lens                    -- lens+import Data.Aeson.Lens                 -- lens-aeson+import Control.Monad                   -- base+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+++main :: IO ()+main = withConnection $ \conn -> do+  sessions <- newIORef []+  simpleHTTP nullConf $ msum+    [ dir "authenticate" $ seeOther (link $ apiKey ak <* callback "__YOUR_CALLBACK__") ""+    , dir "save" $ do+        t <- lookText' "token"+        r <- liftIO . lastfm conn . sign s $ getSession <*> token t <*> apiKey ak <* json+        case r ^? folded.key "session".key "key"._String of+          Just sk -> liftIO $ modifyIORef' sessions (sk:)+          Nothing -> return ()+        ok "Saved."+    , dir "show" $ liftIO (readIORef sessions) >>= ok . show+    ]+ where+  ak = "__YOUR_API_KEY__"+  s  = "__YOUR_SECRET__"
− examples/README.md
@@ -1,82 +0,0 @@-# liblastfm examples--## sort-friends--Shows how to work with lastfm api and async to speed up getting 5 most compliable friends.--  * [async][async]--  * [lens-aeson][lens-aeson]-------## multitag-search--Shows how to work lastfm api around to get search for multiple tags at once.--  * [aeson][aeson]-------## playcount--Shows how to get user playcount.--  * [aeson][aeson]--  * [lens-aeson][lens-aeson]-------## recommendations--Shows how to use signed queries--  * [lens-aeson][lens-aeson]-------## desktop-authentication--Demonstrates [desktop application authentication][last.fm/desktop] flow.--  * [lens-aeson][lens-aeson]-------## web-authentication--Demonstrates [web application authentication][last.fm/web] flow.--  * [happstack-server][happstack-server]-------## mobile-authentication--Demonstrates [mobile application authentication][last.fm/mobile] flow.--  * [lens-aeson][lens-aeson]-------## [scrobblers][scrobblers]--Advanced scrobbling techniques.--  * [netwire][netwire]--  * [libmpd][libmpd]--  * [lens-aeson][lens-aeson]-- [aeson]: https://hackage.haskell.org/package/aeson- [async]: https://hackage.haskell.org/package/async- [happstack-server]: https://hackage.haskell.org/package/happstack-server- [lens-aeson]: https://hackage.haskell.org/package/lens-aeson- [libmpd]: https://hackage.haskell.org/package/libmpd- [netwire]: https://hackage.haskell.org/package/netwire- [scrobblers]: https://github.com/supki/scrobblers- [last.fm/desktop]: http://www.last.fm/api/desktopauth- [last.fm/web]: http://www.last.fm/api/webauth- [last.fm/mobile]: http://www.last.fm/api/mobileauth
− examples/desktop-authentication.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--- | Desktop application authentication flow example------ 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---main :: IO ()-main = do-  r <- lastfm $ getToken <*> apiKey ak <* json-  for_ (r ^? folded.key "token"._String) $ \t -> do-    putStrLn $ "approve: " ++ link (apiKey ak <* token t)-    _ <- getChar-    r' <- lastfm . sign s $ getSession <*> token t <*> apiKey ak <* json-    for_ (r' ^? folded.key "session".key "key"._String) $ \sk ->-      putStrLn $ "session key: " ++ unpack sk- where-  ak = "__YOUR_API_KEY__"-  s  = "__YOUR_SECRET__"
− examples/liblastfm-examples.cabal
@@ -1,105 +0,0 @@-name: liblastfm-examples-version: 0.4.1.0-synopsis: Examples for liblastfm-license: MIT-license-file: LICENSE-author: Matvey Aksenov, Dmitry Malikov-maintainer: Matvey Aksenov <matvey.aksenov@gmail.com>-category: Network APIs-description: Haddock documentation isn't enough to get feeling of the library-cabal-version: >= 1.10-build-type: Simple--executable sort-friends-  default-language: Haskell2010-  build-depends:-      base             >= 4 && < 5-    , liblastfm        >= 0.4.0.0-    , async-    , text-    , aeson-    , lens             >= 4.4-    , lens-aeson       >= 1.0.0.1-  main-is: sort-friends.hs-  ghc-options:-    -Wall-    -fno-warn-unused-do-bind-    -threaded--executable multitag-search-  default-language: Haskell2010-  build-depends:-      base             >= 4 && < 5-    , liblastfm        >= 0.4.0.0-    , text-    , lens             >= 4.4-    , aeson-  main-is: multitag-search.hs-  ghc-options:-    -Wall-    -fno-warn-unused-do-bind--executable web-authentication-  default-language: Haskell2010-  build-depends:-      base             >= 4 && < 5-    , liblastfm        >= 0.4.0.0-    , lens             >= 4.4-    , lens-aeson       >= 1.0.0.1-    , happstack-server >= 7.3-    , transformers-  main-is: web-authentication.hs-  ghc-options:-    -Wall-    -fno-warn-unused-do-bind--executable desktop-authentication-  default-language: Haskell2010-  build-depends:-      base             >= 4 && < 5-    , liblastfm        >= 0.4.0.0-    , text-    , lens             >= 4.4-    , lens-aeson       >= 1.0.0.1-  main-is: desktop-authentication.hs-  ghc-options:-    -Wall-    -fno-warn-unused-do-bind--executable mobile-authentication-  default-language: Haskell2010-  build-depends:-      base             >= 4 && < 5-    , liblastfm        >= 0.4.0.0-    , lens             >= 4.4-    , lens-aeson       >= 1.0.0.1-    , text-  main-is: mobile-authentication.hs-  ghc-options:-    -Wall--executable playcount-  default-language: Haskell2010-  build-depends:-      base             >= 4 && < 5-    , liblastfm        >= 0.4.0.0-    , text-    , lens             >= 4.4-    , lens-aeson       >= 1.0.0.1-  main-is: playcount.hs-  ghc-options:-    -Wall-    -fno-warn-unused-do-bind--executable recommendations-  default-language: Haskell2010-  build-depends:-      base             >= 4 && < 5-    , liblastfm        >= 0.4.0.0-    , text-    , aeson-    , lens             >= 4.4-    , lens-aeson       >= 1.0.0.1-  main-is: recommendations.hs-  ghc-options: -Wall-               -fno-warn-unused-do-bind
− examples/mobile-authentication.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--- | Mobile application authentication flow example------ 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--main :: IO ()-main = do-  r <- lastfm . sign s $ getMobileSession <*> username u <*> password p <*> apiKey ak <* json-  let maybeSk = r ^? folded.key "session".key "key"._String-  Text.putStrLn $ case maybeSk of-    Just sk -> "Mobile session key: " `Text.append` sk-    Nothing -> "Mobile session key wasn't retrieved, something goes wrong"- where-  ak = "__YOUR_API_KEY__"-  s  = "__YOUR_SECRET__"-  u  = "__USERNAME__"-  p  = "__PASSWORD__"
− examples/multitag-search.hs
@@ -1,43 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{- This example shows how to do multitag search even- - if it's not provided by Lastfm API- -- - Sample output:- -- - $> ./examples/multitag-search.hs- - 坂本真綾- - KOTOKO- - Lisa- - 茅原実里- -}-import           Data.Aeson.Types          -- aeson-import           Data.List (intersect)     -- base-import           Data.Maybe                -- base-import           Data.Text (Text)          -- text-import qualified Data.Text.IO as Text      -- text-import           Network.Lastfm            -- liblastfm-import qualified Network.Lastfm.Tag as Tag -- liblastfm--{-# ANN module ("HLint: ignore Use camelCase" :: String) #-}---main :: IO ()-main = mapM_ Text.putStrLn =<< get_artists multitags---get_artists :: [Text] -> IO [Text]-get_artists ts = do-  names <- mapM request ts-  case catMaybes names of-    [] -> return []-    ns -> return (foldl1 intersect ns)- where-  request = fmap (either (const Nothing) (parseMaybe gta)) . query--  query t = lastfm $ Tag.getTopArtists <*> tag t <* limit 100 <*> apiKey "29effec263316a1f8a97f753caaa83e0" <* json--gta :: Value -> Parser [Text]-gta o = parseJSON o >>= (.: "topartists") >>= (.: "artist") >>= mapM (.: "name")--multitags :: [Text]-multitags = ["j-pop", "anime", "anime-ost"]
− examples/playcount.hs
@@ -1,31 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{- This example shows how to get user playcount with lens-aeson- -- - Sample session:- -- - $> ./examples/playcount.hs- - >>> MCDOOMDESTROYER- - 96698- - >>> smpcln- - 95518- - >>> ^C- - $>- -}-import           Control.Lens                -- lens-import           Data.Aeson.Lens             -- lens-aeson-import           Control.Monad               -- base-import           Data.Maybe                  -- 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           System.IO (hFlush, stdout)  -- base---main :: IO ()-main = forever $ Text.putStr ">>> " >> hFlush stdout >> Text.getLine >>= playcount >>= Text.putStrLn--playcount :: Text -> IO Text-playcount u =-  lastfm (User.getInfo <*> user u <*> apiKey "29effec263316a1f8a97f753caaa83e0" <* json) <&> \rsp ->-    fromMaybe "Playcount not found" (rsp ^? folded.key "user".key "playcount"._String)
− examples/recommendations.hs
@@ -1,48 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}-{- This example shows how to use signed queries- - to get user recommendations- -- - Sample output:- -- - Roger Rotor- - Neurosis- - Asche- - SKYHARBOR- - Cylob- - 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---main :: IO ()-main = do-  r <- for [1..pages] $ \p -> parse `fmap` query (User.getRecommendedArtists <* page p)-  mapM_ Text.putStrLn (concat r)---- Construct signed query-query :: Request JSON (APIKey -> SessionKey -> Sign) -> IO (Either LastfmError Value)-query r = lastfm $ sign secret (r <*> ak <*> sk <* json)- where-  ak     = apiKey "__YOUR_API_KEY__"-  sk     = sessionKey "__YOUR_SESSION_KEY__"-  secret = "__YOUR_SECRET__"---- Other query parameters-total, pages :: Int64-total = 250-pages = total `div` 50 -- 50 is the default number of recommendations per page---- Parse artist names from response-parse :: Foldable f => f Value -> [Text]-parse x = x ^.. folded.key "recommendations".key "artist"._Array.folded.key "name"._String
− examples/sort-friends.hs
@@ -1,73 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}-{- This example shows how to extract all- - user friends, get their similarity score and- - then sort them based on it outputting top 5- -- - User friends of which are searched is 'target'- -- - Most of hard work there is on 'aeson-lens' actually :)- -- - Sample output:- -- - % ./examples/sort-friends.hs- - Artvart: 0.9969767332077- - smpcln: 0.9965825676918- - nv0id: 0.86804795265198- - QueenrXy: 0.5932799577713- - GhostOsaka: 0.2875879406929- -- - 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---type Score = Text---main :: IO ()-main = pages >>= scores . names >>= pretty- where-  names x = x ^.. folded.folded.key "friends".key "user"._Array.folded.key "name"._String--pages :: IO [Either LastfmError Value]-pages = do-  first <- query (User.getFriends <*> user target)-  let ps = fromMaybe 1 (preview total first)-  (first :) <$> forConcurrently [2..ps] (\p -> query (User.getFriends <*> user target <* page p))- where-  total = folded.key "friends".key "@attr".key "totalPages"._String.unpacked.to readMaybe.folded--scores :: [Text] -> IO [(Text, Score)]-scores xs = zip xs <$> forConcurrently xs (\x -> do-  r <- query (Tasteometer.compare (user target) (user x))-  return (fromMaybe "0" (preview score r)))- where-  score = folded.key "comparison".key "result".key "score"._String--forConcurrently :: [a] -> (a -> IO b) -> IO [b]-forConcurrently = flip mapConcurrently--pretty :: [(Text, Score)] -> IO ()-pretty = mapM_ (\(n,s) -> Text.putStrLn $ n <> ": " <> s) . take 5 . sortBy (flip compare `on` snd)--query :: Request JSON (APIKey -> Ready) -> IO (Either LastfmError Value)-query r = lastfm (r <*> apiKey "234fc6e0f41f6ef99b7bd62ebaf8d318" <* json)--target :: Text-target = "MCDOOMDESTROYER"
− examples/web-authentication.hs
@@ -1,32 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--- | Web application authentication flow example------ Please remember to substitute __YOUR_API_KEY__,--- __YOUR_SECRET__ and __YOUR_CALLBACK__ for real values-import Control.Lens                    -- lens-import Data.Aeson.Lens                 -- lens-aeson-import Control.Monad                   -- base-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---main :: IO ()-main = do-  sessions <- newIORef []-  simpleHTTP nullConf $ msum-    [ dir "authenticate" $ seeOther (link $ apiKey ak <* callback "__YOUR_CALLBACK__") ""-    , dir "save" $ do-        t <- lookText' "token"-        r <- liftIO . lastfm . sign s $ getSession <*> token t <*> apiKey ak <* json-        case r ^? folded.key "session".key "key"._String of-          Just sk -> liftIO $ modifyIORef' sessions (sk:)-          Nothing -> return ()-        ok "Saved."-    , dir "show" $ liftIO (readIORef sessions) >>= ok . show-    ]- where-  ak = "__YOUR_API_KEY__"-  s  = "__YOUR_SECRET__"
liblastfm.cabal view
@@ -1,5 +1,5 @@ name:          liblastfm-version:       0.4.1.0+version:       0.5.0 synopsis:      Lastfm API interface license:       MIT license-file:  LICENSE@@ -11,17 +11,17 @@ cabal-version: >= 1.10 build-type:    Simple extra-source-files:-  README.md-  CHANGELOG.md-  examples/README.md-  examples/desktop-authentication.hs-  examples/liblastfm-examples.cabal-  examples/mobile-authentication.hs-  examples/multitag-search.hs-  examples/playcount.hs-  examples/recommendations.hs-  examples/sort-friends.hs-  examples/web-authentication.hs+  README.markdown+  CHANGELOG.markdown+  example/README.markdown+  example/desktop-authentication.hs+  example/liblastfm-examples.cabal+  example/mobile-authentication.hs+  example/multitag-search.hs+  example/playcount.hs+  example/recommendations.hs+  example/sort-friends.hs+  example/web-authentication.hs  source-repository head   type:     git@@ -33,7 +33,8 @@   manual: True  library-  default-language: Haskell2010+  default-language:+    Haskell2010   build-depends:       aeson     , base            >= 4 && < 5@@ -81,22 +82,27 @@ test-suite api   if !flag(test-api)     buildable: False-  default-language: Haskell2010+  default-language:+    Haskell2010+  type:+    exitcode-stdio-1.0   build-depends:       aeson     , base                  >= 4 && < 5     , bytestring     , HUnit+    , http-client+    , http-client-tls     , lens-aeson            >= 1.0.0.1     , lens                  >= 4.4     , liblastfm     , hspec     , text     , xml-html-conduit-lens >= 0.3-  type: exitcode-stdio-1.0   hs-source-dirs:     test/api-  main-is: Spec.hs+  main-is:+    Spec.hs   other-modules:     SpecHelper     Json.AlbumSpec@@ -131,7 +137,10 @@     -fno-warn-unused-do-bind  test-suite spec-  default-language: Haskell2010+  default-language:+    Haskell2010+  type:+    exitcode-stdio-1.0   build-depends:       aeson     , base                    >= 4 && < 5@@ -153,11 +162,11 @@     , void     , xml-conduit     , xml-html-conduit-lens   >= 0.3-  type: exitcode-stdio-1.0   hs-source-dirs:     src     test/spec-  main-is: Spec.hs+  main-is:+    Spec.hs   other-modules:     Network.Lastfm.ResponseSpec   ghc-options:
src/Network/Lastfm/Response.hs view
@@ -10,19 +10,18 @@     -- $sign     Secret(..)   , sign-    -- * Get response-  , Supported-  , Format(..)+    -- * Perform requests+  , Connection+  , withConnection   , lastfm   , lastfm_+  , Supported+  , Format(..)     -- ** Errors   , LastfmError(..)   , _LastfmBadResponse   , _LastfmEncodedError   , _LastfmHttpError-    -- ** Internal-  , lastfmWith-  , finalize #ifdef TEST   , parse   , md5@@ -47,8 +46,8 @@ 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 N-import qualified Network.HTTP.Client.TLS as N+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 @@ -115,7 +114,7 @@     -- | last.fm error code and message string   | LastfmEncodedError Int Text     -- | wrapped http-conduit exception-  | LastfmHttpError N.HttpException+  | LastfmHttpError Http.HttpException     deriving (Show, Typeable)  -- | Admittedly, this isn't the best 'Eq' instance ever@@ -165,7 +164,7 @@ -- | This is a @ Prism' 'LastfmError' 'C.HttpException' @ in disguise _LastfmHttpError   :: (Choice p, Applicative m, AsLastfmError e)-  => p N.HttpException (m N.HttpException) -> p e (m 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@@ -202,27 +201,36 @@ md5 = T.pack . show . (hash' :: Strict.ByteString -> MD5Digest) . T.encodeUtf8  --- | Send the 'Request' and parse the 'Response'-lastfm :: Supported f r => Request f Ready -> IO (Either LastfmError r)-lastfm = lastfmWith parse . finalize+-- | Lastfm connection manager+newtype Connection = Connection Http.Manager --- | Send the 'Request' without parsing the 'Response'-lastfm_ :: Supported f r => Request f Ready -> IO (Either LastfmError ())-lastfm_ = lastfmWith (\_ -> Right ()) . finalize+-- | 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 f = Http.withManager Http.tlsManagerSettings (f . Connection) +-- | 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-  => (Lazy.ByteString -> Either LastfmError a)+  => Connection+  -> (Lazy.ByteString -> Either LastfmError a)   -> R f   -> IO (Either LastfmError a)-lastfmWith p r = N.withManager N.tlsManagerSettings $ \manager -> do-  req <- N.parseUrl (render r)+lastfmWith (Connection man) p r = do+  req <- Http.parseUrl (render r)   let req' = req-       { N.method          = _method r-       , N.responseTimeout = Just 10000000+       { Http.method          = _method r+       , Http.responseTimeout = Just 10000000        }-  p . N.responseBody <$> N.httpLbs req' manager+  p . Http.responseBody <$> Http.httpLbs req' man  `catch`   (return . Left) 
test/api/Json/PlaylistSpec.hs view
@@ -26,7 +26,7 @@     key "playlists".key "playlist".key "title"._String    it "addTrack" $ do-    r <- lastfm $ getPlaylists <*> user "liblastfm" <*> ak' <* json+    r <- lastfm man $ getPlaylists <*> user "liblastfm" <*> ak' <* json     case r of       Left e ->         assertFailure (printf "last.fm error: %s" (show e))
test/api/SpecHelper.hs view
@@ -21,21 +21,26 @@   , privateAPIKey   , privateSessionKey   , privateSecret+    -- $really-awful+  , man   ) where -import Control.Exception (Exception, throwIO)-import Control.Lens-import Data.Aeson (Value)-import Data.Aeson.Lens-import Data.Text (Text, pack)-import Data.Typeable (Typeable)-import Network.Lastfm-import System.Environment-import System.IO.Unsafe (unsafePerformIO)-import Test.Hspec-import Test.HUnit (assertFailure)-import Text.Printf-import Text.Xml.Lens+import           Control.Exception (Exception, throwIO)+import           Control.Lens+import           Data.Aeson (Value)+import           Data.Aeson.Lens+import           Data.Text (Text, pack)+import           Data.Typeable (Typeable)+import qualified Network.HTTP.Client as Http+import qualified Network.HTTP.Client.TLS as Http+import           Network.Lastfm+import           System.Environment+import           System.IO.Unsafe (unsafePerformIO)+import           Test.Hspec+import           Test.HUnit (assertFailure)+import           Text.Printf+import           Text.Xml.Lens+import           Unsafe.Coerce (unsafeCoerce)  infixl 1 `shouldHaveJson`, `shouldHaveXml` @@ -43,7 +48,7 @@ -- | Inspect 'Response' with 'Query' shouldHaveResponse :: (Show r, Supported f r) => Request f Ready -> Fold r a -> Expectation shouldHaveResponse q l = do-  r <- lastfm q+  r <- lastfm man q   case preview (_Right.l) r of     Just _  -> return ()     Nothing -> assertFailure (printf "Query failed on %s" (show r))@@ -96,3 +101,8 @@   case mv of     Just v  -> return (pack v)     Nothing -> throwIO (EnvironmentMissing var)++-- $really-awful++man :: Connection+man = unsafeCoerce (unsafePerformIO (Http.newManager Http.tlsManagerSettings))
test/api/Xml/PlaylistSpec.hs view
@@ -25,7 +25,7 @@     root.node "playlists".node "playlist".node "title".text    it "addTrack" $ do-    r <- lastfm $ getPlaylists <*> user "liblastfm" <*> ak' <* Network.Lastfm.xml+    r <- lastfm man $ getPlaylists <*> user "liblastfm" <*> ak' <* Network.Lastfm.xml     case r of       Left e ->         assertFailure (printf "last.fm error: %s" (show e))