liblastfm 0.1.1.1 → 0.1.1.2
raw patch · 15 files changed
+1010/−2 lines, 15 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- liblastfm.cabal +16/−2
- tests/Album.hs +90/−0
- tests/Artist.hs +140/−0
- tests/Chart.hs +47/−0
- tests/Common.hs +20/−0
- tests/Event.hs +49/−0
- tests/Geo.hs +70/−0
- tests/Group.hs +53/−0
- tests/Library.hs +68/−0
- tests/Playlist.hs +39/−0
- tests/Tag.hs +67/−0
- tests/Tasteometer.hs +34/−0
- tests/Track.hs +132/−0
- tests/User.hs +150/−0
- tests/Venue.hs +35/−0
liblastfm.cabal view
@@ -1,5 +1,5 @@ name: liblastfm-version: 0.1.1.1+version: 0.1.1.2 synopsis: Lastfm API interface license: MIT license-file: LICENSE@@ -59,8 +59,22 @@ text, liblastfm type: exitcode-stdio-1.0- main-is: json.hs hs-source-dirs: tests+ main-is: json.hs+ other-modules: Common+ Venue+ User+ Track+ Tasteometer+ Tag+ Playlist+ Library+ Group+ Geo+ Event+ Chart+ Artist+ Album ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-orphans
+ tests/Album.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Album (auth, noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Album+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++auth ∷ Request JSON Sign APIKey → Request JSON Sign SessionKey → Secret → [Test]+auth ak sk s =+ [ testCase "Album.addTags" testAddTags+ , testCase "Album.getTags-authenticated" testGetTagsAuth+ , testCase "Album.removeTag" testRemoveTag+ , testCase "Album.share" testShare+ ]+ where+ testAddTags = check ok . sign s $+ addTags <*> artist "Pink Floyd" <*> album "The Wall" <*> tags ["70s", "awesome", "classic"]+ <*> ak <*> sk++ testGetTagsAuth = check gt . sign s $+ getTags <*> artist "Pink Floyd" <*> album "The Wall"+ <*> ak <* sk++ testRemoveTag = check ok . sign s $+ removeTag <*> artist "Pink Floyd" <*> album "The Wall" <*> tag "awesome"+ <*> ak <*> sk++ testShare = check ok . sign s $+ share <*> album "Jerusalem" <*> artist "Sleep" <*> recipient "liblastfm" <* message "Just listen!"+ <*> ak <*> sk+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Album.getBuyLinks" testGetBuylinks+ , testCase "Album.getBuyLinks_mbid" testGetBuylinks_mbid+ , testCase "Album.getInfo" testGetInfo+ , testCase "Album.getInfo_mbid" testGetInfo_mbid+ , testCase "Album.getShouts" testGetShouts+ , testCase "Album.getShouts_mbid" testGetShouts_mbid+ , testCase "Album.getTags" testGetTags+ , testCase "Album.getTags_mbid" testGetTags_mbid+ , testCase "Album.getTopTags" testGetTopTags+ , testCase "Album.getTopTags_mbid" testGetTopTags_mbid+ , testCase "Album.search" testSearch+ ]+ where+ testGetBuylinks = check gbl $+ getBuyLinks <*> country "United Kingdom" <*> artist "Pink Floyd" <*> album "The Wall" <*> ak+ testGetBuylinks_mbid = check gbl $+ getBuyLinks <*> country "United Kingdom" <*> mbid "3a16c04b-922b-35c5-a29b-cbe9111fbe79" <*> ak++ testGetInfo = check gi $+ getInfo <*> artist "Pink Floyd" <*> album "The Wall" <*> ak+ testGetInfo_mbid = check gi $+ getInfo <*> mbid "3a16c04b-922b-35c5-a29b-cbe9111fbe79" <*> ak++ testGetShouts = check gs $+ getShouts <*> artist "Pink Floyd" <*> album "The Wall" <* limit 7 <*> ak+ testGetShouts_mbid = check gs $+ getShouts <*> mbid "3a16c04b-922b-35c5-a29b-cbe9111fbe79" <* limit 7 <*> ak++ testGetTags = check gt $+ getTags <*> artist "Pink Floyd" <*> album "The Wall" <* user "liblastfm" <*> ak+ testGetTags_mbid = check gt $+ getTags <*> mbid "3a16c04b-922b-35c5-a29b-cbe9111fbe79" <* user "liblastfm" <*> ak++ testGetTopTags = check gtt $+ getTopTags <*> artist "Pink Floyd" <*> album "The Wall" <*> ak+ testGetTopTags_mbid = check gtt $+ getTopTags <*> mbid "3a16c04b-922b-35c5-a29b-cbe9111fbe79" <*> ak++ testSearch = check se $+ search <*> album "wall" <* limit 5 <*> ak+++gbl, gi, gs, gt, gtt, se ∷ Value → Parser [String]+gbl o = parseJSON o >>= (.: "affiliations") >>= (.: "physicals") >>= (.: "affiliation") >>= mapM (.: "supplierName")+gi o = parseJSON o >>= (.: "album") >>= (.: "toptags") >>= (.: "tag") >>= mapM (.: "name")+gs o = parseJSON o >>= (.: "shouts") >>= (.: "shout") >>= mapM (.: "body")+gt o = parseJSON o >>= (.: "tags") >>= (.: "tag") >>= mapM (.: "name")+gtt o = parseJSON o >>= (.: "toptags") >>= (.: "tag") >>= mapM (.: "count")+se o = parseJSON o >>= (.: "results") >>= (.: "albummatches") >>= (.: "album") >>= mapM (.: "name")
+ tests/Artist.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Artist (auth, noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Artist+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++auth ∷ Request JSON Sign APIKey → Request JSON Sign SessionKey → Secret → [Test]+auth ak sk s =+ [ testCase "Artist.addTags" testAddTags+ , testCase "Artist.getTags-authenticated" testGetTagsAuth+ , testCase "Artist.removeTag" testRemoveTag+ , testCase "Artist.share" testShare+ ]+ where+ testAddTags = check ok . sign s $+ addTags <*> artist "Егор Летов" <*> tags ["russian", "black metal"] <*> ak <*> sk++ testGetTagsAuth = check gt . sign s $+ getTags <*> artist "Егор Летов" <*> ak <* sk++ testRemoveTag = check ok . sign s $+ removeTag <*> artist "Егор Летов" <*> tag "russian" <*> ak <*> sk++ testShare = check ok . sign s $+ share <*> artist "Sleep" <*> recipient "liblastfm" <* message "Just listen!" <*> ak <*> sk+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Artist.getCorrection" testGetCorrection+ , testCase "Artist.getEvents" testGetEvents+ , testCase "Artist.getEvents_mbid" testGetEvents_mbid+ , testCase "Artist.getInfo" testGetInfo+ , testCase "Artist.getInfo_mbid" testGetInfo_mbid+ , testCase "Artist.getPastEvents" testGetPastEvents+ , testCase "Artist.getPastEvents_mbid" testGetPastEvents_mbid+ , testCase "Artist.getPodcast" testGetPodcast+ , testCase "Artist.getPodcast_mbid" testGetPodcast_mbid+ , testCase "Artist.getShouts" testGetShouts+ , testCase "Artist.getShouts_mbid" testGetShouts_mbid+ , testCase "Artist.getSimilar" testGetSimilar+ , testCase "Artist.getSimilar_mbid" testGetSimilar_mbid+ , testCase "Artist.getTags" testGetTags+ , testCase "Artist.getTags_mbid" testGetTags_mbid+ , testCase "Artist.getTopAlbums" testGetTopAlbums+ , testCase "Artist.getTopAlbums_mbid" testGetTopAlbums_mbid+ , testCase "Artist.getTopFans" testGetTopFans+ , testCase "Artist.getTopFans_mbid" testGetTopFans_mbid+ , testCase "Artist.getTopTags" testGetTopTags+ , testCase "Artist.getTopTags_mbid" testGetTopTags_mbid+ , testCase "Artist.getTopTracks" testGetTopTracks+ , testCase "Artist.getTopTracks_mbid" testGetTopTracks_mbid+ , testCase "Artist.search" testSearch+ ]+ where+ testGetCorrection = check gc $+ getCorrection <*> artist "Meshugah" <*> ak++ testGetEvents = check ge $+ getEvents <*> artist "Meshuggah" <* limit 2 <*> ak+ testGetEvents_mbid = check ge $+ getEvents <*> mbid "cf8b3b8c-118e-4136-8d1d-c37091173413" <* limit 2 <*> ak++ testGetInfo = check gin $+ getInfo <*> artist "Meshuggah" <*> ak+ testGetInfo_mbid = check gin $+ getInfo <*> mbid "cf8b3b8c-118e-4136-8d1d-c37091173413" <*> ak++ testGetPastEvents = check gpe $+ getPastEvents <*> artist "Meshuggah" <* autocorrect True <*> ak+ testGetPastEvents_mbid = check gpe $+ getPastEvents <*> mbid "cf8b3b8c-118e-4136-8d1d-c37091173413" <* autocorrect True <*> ak++ testGetPodcast = check gp $+ getPodcast <*> artist "Meshuggah" <*> ak+ testGetPodcast_mbid = check gp $+ getPodcast <*> mbid "cf8b3b8c-118e-4136-8d1d-c37091173413" <*> ak++ testGetShouts = check gs $+ getShouts <*> artist "Meshuggah" <* limit 5 <*> ak+ testGetShouts_mbid = check gs $+ getShouts <*> mbid "cf8b3b8c-118e-4136-8d1d-c37091173413" <* limit 5 <*> ak++ testGetSimilar = check gsi $+ getSimilar <*> artist "Meshuggah" <* limit 3 <*> ak+ testGetSimilar_mbid = check gsi $+ getSimilar <*> mbid "cf8b3b8c-118e-4136-8d1d-c37091173413" <* limit 3 <*> ak++ testGetTags = check gt $+ getTags <*> artist "Егор Летов" <* user "liblastfm" <*> ak+ testGetTags_mbid = check gt $+ getTags <*> mbid "cfb3d32e-d095-4d63-946d-9daf06932180" <* user "liblastfm" <*> ak++ testGetTopAlbums = check gta $+ getTopAlbums <*> artist "Meshuggah" <* limit 3 <*> ak+ testGetTopAlbums_mbid = check gta $+ getTopAlbums <*> mbid "cf8b3b8c-118e-4136-8d1d-c37091173413" <* limit 3 <*> ak++ testGetTopFans = check gtf $+ getTopFans <*> artist "Meshuggah" <*> ak+ testGetTopFans_mbid = check gtf $+ getTopFans <*> mbid "cf8b3b8c-118e-4136-8d1d-c37091173413" <*> ak++ testGetTopTags = check gtt $+ getTopTags <*> artist "Meshuggah" <*> ak+ testGetTopTags_mbid = check gtt $+ getTopTags <*> mbid "cf8b3b8c-118e-4136-8d1d-c37091173413" <*> ak++ testGetTopTracks = check gttr $+ getTopTracks <*> artist "Meshuggah" <* limit 3 <*> ak+ testGetTopTracks_mbid = check gttr $+ getTopTracks <*> mbid "cf8b3b8c-118e-4136-8d1d-c37091173413" <* limit 3 <*> ak++ testSearch = check se $+ search <*> artist "Mesh" <* limit 3 <*> ak+++gc, gin, gp ∷ Value → Parser String+ge, gpe, gs, gsi, gt, gta, gtf, gtt, gttr, se ∷ Value → Parser [String]+gc o = parseJSON o >>= (.: "corrections") >>= (.: "correction") >>= (.: "artist") >>= (.: "name")+ge o = parseJSON o >>= (.: "events") >>= (.: "event") >>= mapM (\o' → (o' .: "venue") >>= (.: "name"))+gin o = parseJSON o >>= (.: "artist") >>= (.: "stats") >>= (.: "listeners")+gp o = parseJSON o >>= (.: "rss") >>= (.: "channel") >>= (.: "description")+gpe o = parseJSON o >>= (.: "events") >>= (.: "event") >>= mapM (.: "title")+gs o = parseJSON o >>= (.: "shouts") >>= (.: "shout") >>= mapM (.: "author")+gsi o = parseJSON o >>= (.: "similarartists") >>= (.: "artist") >>= mapM (.: "name")+gt o = parseJSON o >>= (.: "tags") >>= (.: "tag") >>= mapM (.: "name")+gta o = parseJSON o >>= (.: "topalbums") >>= (.: "album") >>= mapM (.: "name")+gtf o = parseJSON o >>= (.: "topfans") >>= (.: "user") >>= mapM (.: "name")+gtt o = parseJSON o >>= (.: "toptags") >>= (.: "tag") >>= mapM (.: "name")+gttr o = parseJSON o >>= (.: "toptracks") >>= (.: "track") >>= mapM (.: "name")+se o = parseJSON o >>= (.: "results") >>= (.: "artistmatches") >>= (.: "artist") >>= mapM (.: "name")
+ tests/Chart.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Chart (noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Chart+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Chart.getHypedArtists" testGetHypedArtists+ , testCase "Chart.getHypedTracks" testGetHypedTracks+ , testCase "Chart.getLovedTracks" testGetLovedTracks+ , testCase "Chart.getTopArtists" testGetTopArtists+ , testCase "Chart.getTopTags" testGetTopTags+ , testCase "Chart.getTopTracks" testGetTopTracks+ ]+ where+ testGetHypedArtists = check ga $+ getHypedArtists <* limit 3 <*> ak++ testGetHypedTracks = check gt $+ getHypedTracks <* limit 2 <*> ak++ testGetLovedTracks = check gt $+ getLovedTracks <* limit 3 <*> ak++ testGetTopArtists = check ga $+ getTopArtists <* limit 4 <*> ak++ testGetTopTags = check gta $+ getTopTags <* limit 5 <*> ak++ testGetTopTracks = check gt $+ getTopTracks <* limit 2 <*> ak+++ga, gt, gta ∷ Value → Parser [String]+ga o = parseJSON o >>= (.: "artists") >>= (.: "artist") >>= mapM (.: "name")+gt o = parseJSON o >>= (.: "tracks") >>= (.: "track") >>= mapM (.: "name")+gta o = parseJSON o >>= (.: "tags") >>= (.: "tag") >>= mapM (.: "name")
+ tests/Common.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Common where++import Data.Aeson.Types+import Network.Lastfm+import Test.HUnit+++check ∷ (Value → Parser a) → Request JSON Send Ready → Assertion+check p q = do+ r ← lastfm q+ case parse p `fmap` r of+ Just (Success _) → assertBool "success" True+ _ → assertFailure $ "Got: " ++ show r+++ok ∷ Value → Parser String+ok o = parseJSON o >>= (.: "status")
+ tests/Event.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Event (auth, noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Event+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++auth ∷ Request JSON Sign APIKey → Request JSON Sign SessionKey → Secret → [Test]+auth ak sk s =+ [ testCase "Event.attend" testAttend+ , testCase "Event.share" testShare+ ]+ where+ testAttend = check ok . sign s $+ attend <*> event 3142549 <*> status Attending <*> ak <*> sk++ testShare = check ok . sign s $+ share <*> event 3142549 <*> recipient "liblastfm" <* message "Just listen!" <*> ak <*> sk+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Event.getAttendees" testGetAttendees+ , testCase "Event.getInfo" testGetInfo+ , testCase "Event.getShouts" testGetShouts+ ]+ where+ testGetAttendees = check ga $+ getAttendees <*> event 3142549 <* limit 2 <*> ak++ testGetInfo = check gi $+ getInfo <*> event 3142549 <*> ak++ testGetShouts = check gs $+ getShouts <*> event 3142549 <* limit 1 <*> ak+++gi, gs ∷ Value → Parser String+ga ∷ Value → Parser [String]+ga o = parseJSON o >>= (.: "attendees") >>= (.: "user") >>= mapM (.: "name")+gi o = parseJSON o >>= (.: "event") >>= (.: "venue") >>= (.: "location") >>= (.: "city")+gs o = parseJSON o >>= (.: "shouts") >>= (.: "shout") >>= (.: "body")
+ tests/Geo.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Geo (noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Geo+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Geo.getEvents" testGetEvents+ , testCase "Geo.getMetroArtistChart" testGetMetroArtistChart+ , testCase "Geo.getMetroHypeArtistChart" testGetMetroHypeArtistChart+ , testCase "Geo.getMetroHypeTrackChart" testGetMetroHypeTrackChart+ , testCase "Geo.getMetroTrackChart" testGetMetroTrackChart+ , testCase "Geo.getMetroUniqueArtistChart" testGetMetroUniqueArtistChart+ , testCase "Geo.getMetroUniqueTrackChart" testGetMetroUniqueTrackChart+ , testCase "Geo.getMetroWeeklyChartlist" testGetMetroWeeklyChartlist+ , testCase "Geo.getMetros" testGetMetros+ , testCase "Geo.getTopArtists" testGetTopArtists+ , testCase "Geo.getTopTracks" testGetTopTracks+ ]+ where+ testGetEvents = check ge $+ getEvents <* location "Moscow" <* limit 5 <*> ak++ testGetMetroArtistChart = check ga $+ getMetroArtistChart <*> metro "Saint Petersburg" <*> country "Russia" <*> ak++ testGetMetroHypeArtistChart = check ga $+ getMetroHypeArtistChart <*> metro "New York" <*> country "United States" <*> ak++ testGetMetroHypeTrackChart = check gt $+ getMetroHypeTrackChart <*> metro "Ufa" <*> country "Russia" <*> ak++ testGetMetroTrackChart = check gt $+ getMetroTrackChart <*> metro "Boston" <*> country "United States" <*> ak++ testGetMetroUniqueArtistChart = check ga $+ getMetroUniqueArtistChart <*> metro "Minsk" <*> country "Belarus" <*> ak++ testGetMetroUniqueTrackChart = check gt $+ getMetroUniqueTrackChart <*> metro "Moscow" <*> country "Russia" <*> ak++ testGetMetroWeeklyChartlist = check gc $+ getMetroWeeklyChartlist <*> metro "Moscow" <*> ak++ testGetMetros = check gm $+ getMetros <* country "Russia" <*> ak++ testGetTopArtists = check ga $+ getTopArtists <*> country "Belarus" <* limit 3 <*> ak++ testGetTopTracks = check gt $+ getTopTracks <*> country "Ukraine" <* limit 2 <*> ak+++ga, ge, gm, gt ∷ Value → Parser [String]+gc ∷ Value → Parser [(String, String)]+ga o = parseJSON o >>= (.: "topartists") >>= (.: "artist") >>= mapM (.: "name")+gc o = parseJSON o >>= (.: "weeklychartlist") >>= (.: "chart") >>= mapM (\t → (,) <$> (t .: "from") <*> (t .: "to"))+ge o = parseJSON o >>= (.: "events") >>= (.: "event") >>= mapM (.: "id")+gm o = parseJSON o >>= (.: "metros") >>= (.: "metro") >>= mapM (.: "name")+gt o = parseJSON o >>= (.: "toptracks") >>= (.: "track") >>= mapM (.: "name")
+ tests/Group.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Group (noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Group+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Group.getHype" testGetHype+ , testCase "Group.getMembers" testGetMembers+ , testCase "Group.getWeeklyAlbumChart" testGetWeeklyAlbumChart+ , testCase "Group.getWeeklyArtistChart" testGetWeeklyArtistChart+ , testCase "Group.getWeeklyChartList" testGetWeeklyChartList+ , testCase "Group.getWeeklyTrackChart" testGetWeeklyTrackChart+ ]+ where+ g = "People with no social lives that listen to more music than is healthy who are slightly scared of spiders and can never seem to find a pen"++ testGetHype = check gh $+ getHype <*> group g <*> ak++ testGetMembers = check gm $+ getMembers <*> group g <* limit 10 <*> ak++ testGetWeeklyAlbumChart = check ga $+ getWeeklyAlbumChart <*> group g <*> ak++ testGetWeeklyArtistChart = check gar $+ getWeeklyArtistChart <*> group g <*> ak++ testGetWeeklyChartList = check gc $+ getWeeklyChartList <*> group g <*> ak++ testGetWeeklyTrackChart = check gt $+ getWeeklyTrackChart <*> group g <*> ak+++ga, gar, gh, gm, gt ∷ Value → Parser [String]+gc ∷ Value → Parser [(String, String)]+ga o = parseJSON o >>= (.: "weeklyalbumchart") >>= (.: "album") >>= mapM (.: "playcount")+gar o = parseJSON o >>= (.: "weeklyartistchart") >>= (.: "artist") >>= mapM (.: "name")+gc o = parseJSON o >>= (.: "weeklychartlist") >>= (.: "chart") >>= mapM (\t → (,) <$> (t .: "from") <*> (t .: "to"))+gh o = parseJSON o >>= (.: "weeklyartistchart") >>= (.: "artist") >>= mapM (.: "mbid")+gm o = parseJSON o >>= (.: "members") >>= (.: "user") >>= mapM (.: "name")+gt o = parseJSON o >>= (.: "weeklytrackchart") >>= (.: "track") >>= mapM (.: "url")
+ tests/Library.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Library (auth, noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Library+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++auth ∷ Request JSON Sign APIKey → Request JSON Sign SessionKey → Secret → [Test]+auth ak sk s =+ [ testCase "Library.addAlbum" testAddAlbum+ , testCase "Library.addArtist" testAddArtist+ , testCase "Library.addTrack" testAddTrack+ , testCase "Library.removeAlbum" testRemoveAlbum+ , testCase "Library.removeArtist" testRemoveArtist+ , testCase "Library.removeTrack" testRemoveTrack+ , testCase "Library.removeScrobble" testRemoveScrobble+ ]+ where+ testAddAlbum = check ok . sign s $+ addAlbum <*> artist "Franz Ferdinand" <*> album "Franz Ferdinand" <*> ak <*> sk++ testAddArtist = check ok . sign s $+ addArtist <*> artist "Mobthrow" <*> ak <*> sk++ testAddTrack = check ok . sign s $+ addTrack <*> artist "Eminem" <*> track "Kim" <*> ak <*> sk++ testRemoveAlbum = check ok . sign s $+ removeAlbum <*> artist "Franz Ferdinand" <*> album "Franz Ferdinand" <*> ak <*> sk++ testRemoveArtist = check ok . sign s $+ removeArtist <*> artist "Burzum" <*> ak <*> sk++ testRemoveTrack = check ok . sign s $+ removeTrack <*> artist "Eminem" <*> track "Kim" <*> ak <*> sk++ testRemoveScrobble = check ok . sign s $+ removeScrobble <*> artist "Gojira" <*> track "Ocean" <*> timestamp 1328905590 <*> ak <*> sk+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Library.getAlbums" testGetAlbums+ , testCase "Library.getArtists" testGetArtists+ , testCase "Library.getTracks" testGetTracks+ ]+ where+ testGetAlbums = check ga $+ getAlbums <*> user "smpcln" <* artist "Burzum" <* limit 5 <*> ak++ testGetArtists = check gar $+ getArtists <*> user "smpcln" <* limit 7 <*> ak++ testGetTracks = check gt $+ getTracks <*> user "smpcln" <* artist "Burzum" <* limit 4 <*> ak+++ga, gar, gt ∷ Value → Parser [String]+ga o = parseJSON o >>= (.: "albums") >>= (.: "album") >>= mapM (.: "name")+gar o = parseJSON o >>= (.: "artists") >>= (.: "artist") >>= mapM (.: "name")+gt o = parseJSON o >>= (.: "tracks") >>= (.: "track") >>= mapM (.: "name")
+ tests/Playlist.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Playlist (auth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Playlist+import Network.Lastfm.User+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++auth ∷ Request JSON Sign APIKey → Request JSON Sign SessionKey → Secret → [Test]+auth ak sk s =+ [ testCase "Playlist.create" testCreate -- Order matters.+ , testCase "Playlist.addTrack" testAddTrack+ ]+ where+ ak' = "29effec263316a1f8a97f753caaa83e0"++ testAddTrack =+ do r ← lastfm $ getPlaylists <*> user "liblastfm" <*> apiKey ak' <* json+ case parseMaybe pl =<< r of+ Nothing → error "M"+ Just r' → let pid = read $ head r' in check ok . sign s $+ addTrack <*> playlist pid <*> artist "Ruby my dear" <*> track "Chazz" <*> ak <*> sk++ testCreate = check pn . sign s $+ create <* title "Awesome playlist" <*> ak <*> sk+++pl ∷ Value → Parser [String]+pl o = parseJSON o >>= (.: "playlists") >>= (.: "playlist") >>= mapM (.: "id")++pn ∷ Value → Parser String+pn o = parseJSON o >>= (.: "playlists") >>= (.: "playlist") >>= (.: "title")
+ tests/Tag.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Tag (noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Tag+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Tag.getInfo" testGetInfo+ , testCase "Tag.getSimilar" testGetSimilar+ , testCase "Tag.getTopAlbums" testGetTopAlbums+ , testCase "Tag.getTopArtists" testGetTopArtists+ , testCase "Tag.getTopTags" testGetTopTags+ , testCase "Tag.getTopTracks" testGetTopTracks+ , testCase "Tag.getWeeklyArtistChart" testGetWeeklyArtistChart+ , testCase "Tag.getWeeklyChartList" testGetWeeklyChartList+ , testCase "Tag.search" testSearch+ ]+ where+ testGetInfo = check gi $+ getInfo <*> tag "depressive" <*> ak++ testGetSimilar = check gs $+ getSimilar <*> tag "depressive" <*> ak++ testGetTopAlbums = check gta $+ getTopAlbums <*> tag "depressive" <* limit 2 <*> ak++ testGetTopArtists = check gtar $+ getTopArtists <*> tag "depressive" <* limit 3 <*> ak++ testGetTopTags = check gtt $+ getTopTags <*> ak++ testGetTopTracks = check gttr $+ getTopTracks <*> tag "depressive" <* limit 2 <*> ak++ testGetWeeklyArtistChart = check gwac $+ getWeeklyArtistChart <*> tag "depressive" <* limit 3 <*> ak++ testGetWeeklyChartList = check gc $+ getWeeklyChartList <*> tag "depressive" <*> ak++ testSearch = check se $+ search <*> tag "depressive" <* limit 3 <*> ak+++gi ∷ Value → Parser String+gs, gta, gtar, gtt, gttr, gwac, se ∷ Value → Parser [String]+gc ∷ Value → Parser [(String, String)]+gi o = parseJSON o >>= (.: "tag") >>= (.: "taggings")+gc o = parseJSON o >>= (.: "weeklychartlist") >>= (.: "chart") >>= mapM (\t → (,) <$> (t .: "from") <*> (t .: "to"))+gs o = parseJSON o >>= (.: "similartags") >>= (.: "tag") >>= mapM (.: "name")+gta o = parseJSON o >>= (.: "topalbums") >>= (.: "album") >>= mapM (.: "url")+gtar o = parseJSON o >>= (.: "topartists") >>= (.: "artist") >>= mapM (.: "url")+gtt o = parseJSON o >>= (.: "toptags") >>= (.: "tag") >>= mapM (.: "name")+gttr o = parseJSON o >>= (.: "toptracks") >>= (.: "track") >>= mapM (.: "url")+gwac o = parseJSON o >>= (.: "weeklyartistchart") >>= (.: "artist") >>= mapM (.: "name")+se o = parseJSON o >>= (.: "results") >>= (.: "tagmatches") >>= (.: "tag") >>= mapM (.: "name")
+ tests/Tasteometer.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Tasteometer (noauth) where++import Data.Aeson.Types+import Network.Lastfm+import qualified Network.Lastfm.Tasteometer as T+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Tasteometer.compare" testCompare+ , testCase "Tasteometer.compare'" testCompare'+ , testCase "Tasteometer.compare''" testCompare''+ , testCase "Tasteometer.compare'''" testCompare'''+ ]+ where+ testCompare = check cs $+ T.compare (user "smpcln") (user "MCDOOMDESTROYER") <*> ak+ testCompare' = check cs $+ T.compare (user "smpcln") (artists ["enduser", "venetian snares"]) <*> ak+ testCompare'' = check cs $+ T.compare (artists ["enduser", "venetian snares"]) (user "smpcln") <*> ak+ testCompare''' = check cs $+ T.compare (artists ["enduser", "venetian snares"]) (artists ["enduser", "venetian snares"]) <*> ak+++cs ∷ Value → Parser String+cs o = parseJSON o >>= (.: "comparison") >>= (.: "result") >>= (.: "score")
+ tests/Track.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Track (auth, noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Track+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++auth ∷ Request JSON Sign APIKey → Request JSON Sign SessionKey → Secret → [Test]+auth ak sk s =+ [ testCase "Track.addTags" testAddTags+ , testCase "Track.ban" testBan+ , testCase "Track.love" testLove+ , testCase "Track.removeTag" testRemoveTag+ , testCase "Track.share" testShare+ , testCase "Track.unban" testUnban+ , testCase "Track.unlove" testUnlove+ , testCase "Track.scrobble" testScrobble+ , testCase "Track.updateNowPlaying" testUpdateNowPlaying+ ]+ where+ testAddTags = check ok . sign s $+ addTags <*> artist "Jefferson Airplane" <*> track "White rabbit" <*> tags ["60s", "awesome"] <*> ak <*> sk++ testBan = check ok . sign s $+ ban <*> artist "Eminem" <*> track "Kim" <*> ak <*> sk++ testLove = check ok . sign s $+ love <*> artist "Gojira" <*> track "Ocean" <*> ak <*> sk++ testRemoveTag = check ok . sign s $+ removeTag <*> artist "Jefferson Airplane" <*> track "White rabbit" <*> tag "awesome" <*> ak <*> sk++ testShare = check ok . sign s $+ share <*> artist "Led Zeppelin" <*> track "When the Levee Breaks" <*> recipient "liblastfm" <* message "Just listen!" <*> ak <*> sk++ testUnban = check ok . sign s $+ unban <*> artist "Eminem" <*> track "Kim" <*> ak <*> sk++ testUnlove = check ok . sign s $+ unlove <*> artist "Gojira" <*> track "Ocean" <*> ak <*> sk++ testScrobble = check ss . sign s $+ scrobble <*> artist "Gojira" <*> track "Ocean" <*> timestamp 1300000000 <*> ak <*> sk++ testUpdateNowPlaying = check np . sign s $+ updateNowPlaying <*> artist "Gojira" <*> track "Ocean" <*> ak <*> sk+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Track.getBuylinks" testGetBuylinks+ , testCase "Track.getCorrection" testGetCorrection+ , testCase "Track.getFingerprintMetadata" testGetFingerprintMetadata+ , testCase "Track.getInfo" testGetInfo+ , testCase "Track.getInfo_mbid" testGetInfo_mbid+ , testCase "Track.getShouts" testGetShouts+ , testCase "Track.getShouts_mbid" testGetShouts_mbid+ , testCase "Track.getSimilar" testGetSimilar+ , testCase "Track.getSimilar_mbid" testGetSimilar_mbid+ , testCase "Track.getTags" testGetTags+ , testCase "Track.getTags_mbid" testGetTags_mbid+ , testCase "Track.getTopFans" testGetTopFans+ , testCase "Track.getTopFans_mbid" testGetTopFans_mbid+ , testCase "Track.getTopTags" testGetTopTags+ , testCase "Track.getTopTags_mbid" testGetTopTags_mbid+ , testCase "Track.search" testSearch+ ]+ where+ testGetBuylinks = check gbl $+ getBuyLinks <*> country "United Kingdom" <*> artist "Pink Floyd" <*> track "Brain Damage" <*> ak++ testGetCorrection = check gc $+ getCorrection <*> artist "Pink Ployd" <*> track "Brain Damage" <*> ak++ testGetFingerprintMetadata = check gfm $+ getFingerprintMetadata <*> fingerprint 1234 <*> ak++ testGetInfo = check gi $+ getInfo <*> artist "Pink Floyd" <*> track "Comfortably Numb" <* username "aswalrus" <*> ak+ testGetInfo_mbid = check gi $+ getInfo <*> mbid "b6581c74-0b8c-4981-ab92-7eed0298a4bb" <* username "aswalrus" <*> ak++ testGetShouts = check gsh $+ getShouts <*> artist "Pink Floyd" <*> track "Comfortably Numb" <* limit 7 <*> ak+ testGetShouts_mbid = check gsh $+ getShouts <*> mbid "b6581c74-0b8c-4981-ab92-7eed0298a4bb" <* limit 7 <*> ak++ testGetSimilar = check gsi $+ getSimilar <*> artist "Pink Floyd" <*> track "Comfortably Numb" <* limit 4 <*> ak+ testGetSimilar_mbid = check gsi $+ getSimilar <*> mbid "b6581c74-0b8c-4981-ab92-7eed0298a4bb" <* limit 4 <*> ak++ testGetTags = check gt $+ getTags <*> artist "Jefferson Airplane" <*> track "White Rabbit" <* user "liblastfm" <*> ak+ testGetTags_mbid = check gt $+ getTags <*> mbid "1fc619ee-c612-4b2a-a8dc-bb8f1b8b2d6d" <* user "liblastfm" <*> ak++ testGetTopFans = check gtf $+ getTopFans <*> artist "Pink Floyd" <*> track "Comfortably Numb" <*> ak+ testGetTopFans_mbid = check gtf $+ getTopFans <*> mbid "b6581c74-0b8c-4981-ab92-7eed0298a4bb" <*> ak++ testGetTopTags = check gtt $+ getTopTags <*> artist "Pink Floyd" <*> track "Comfortably Numb" <*> ak+ testGetTopTags_mbid = check gtt $+ getTopTags <*> mbid "b6581c74-0b8c-4981-ab92-7eed0298a4bb" <*> ak++ testSearch = check s' $+ search <*> track "Believe" <* limit 12 <*> ak+++gc, gi, gt, ss, np ∷ Value → Parser String+gbl, gfm, gsh, gsi{-, gta-}, gtf, gtt, s' ∷ Value → Parser [String]+gbl o = parseJSON o >>= (.: "affiliations") >>= (.: "downloads") >>= (.: "affiliation") >>= mapM (.: "supplierName")+gc o = parseJSON o >>= (.: "corrections") >>= (.: "correction") >>= (.: "track") >>= (.: "artist") >>= (.: "name")+gfm o = parseJSON o >>= (.: "tracks") >>= (.: "track") >>= mapM (.: "name")+gi o = parseJSON o >>= (.: "track") >>= (.: "userplaycount")+gsh o = parseJSON o >>= (.: "shouts") >>= (.: "shout") >>= mapM (.: "author")+gsi o = parseJSON o >>= (.: "similartracks") >>= (.: "track") >>= mapM (.: "name")+gt o = parseJSON o >>= (.: "tags") >>= (.: "@attr") >>= (.: "track")+gtf o = parseJSON o >>= (.: "topfans") >>= (.: "user") >>= mapM (.: "name")+gtt o = parseJSON o >>= (.: "toptags") >>= (.: "tag") >>= mapM (.: "name")+s' o = parseJSON o >>= (.: "results") >>= (.: "trackmatches") >>= (.: "track") >>= mapM (.: "name")+ss o = parseJSON o >>= (.: "scrobbles") >>= (.: "scrobble") >>= (.: "track") >>= (.: "#text")+np o = parseJSON o >>= (.: "nowplaying") >>= (.: "track") >>= (.: "#text")
+ tests/User.hs view
@@ -0,0 +1,150 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module User (auth, noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.User+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++auth ∷ Request JSON Sign APIKey → Request JSON Sign SessionKey → Secret → [Test]+auth ak sk s =+ [ testCase "User.getRecentStations" testGetRecentStations+ , testCase "User.getRecommendedArtists" testGetRecommendedArtists+ , testCase "User.getRecommendedEvents" testGetRecommendedEvents+ , testCase "User.shout" testShout+ ]+ where+ testGetRecentStations = check grs . sign s $+ getRecentStations <*> user "liblastfm" <* limit 10 <*> ak <*> sk++ testGetRecommendedArtists = check gra . sign s $+ getRecommendedArtists <* limit 10 <*> ak <*> sk++ testGetRecommendedEvents = check gre . sign s $+ getRecommendedEvents <* limit 10 <*> ak <*> sk++ testShout = check ok . sign s $+ shout <*> user "liblastfm" <*> message "test message" <*> ak <*> sk+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "User.getArtistTracks" testGetArtistTracks+ , testCase "User.getBannedTracks" testGetBannedTracks+ , testCase "User.getEvents" testGetEvents+ , testCase "User.getFriends" testGetFriends+ , testCase "User.getPlayCount" testGetPlayCount+ , testCase "User.getGetLovedTracks" testGetLovedTracks+ , testCase "User.getNeighbours" testGetNeighbours+ , testCase "User.getNewReleases" testGetNewReleases+ , testCase "User.getPastEvents" testGetPastEvents+ , testCase "User.getPersonalTags" testGetPersonalTags+ , testCase "User.getPlaylists" testGetPlaylists+ , testCase "User.getRecentTracks" testGetRecentTracks+ , testCase "User.getShouts" testGetShouts+ , testCase "User.getTopAlbums" testGetTopAlbums+ , testCase "User.getTopArtists" testGetTopArtists+ , testCase "User.getTopTags" testGetTopTags+ , testCase "User.getTopTracks" testGetTopTracks+ , testCase "User.getWeeklyAlbumChart" testGetWeeklyAlbumChart+ , testCase "User.getWeeklyArtistChart" testGetWeeklyArtistChart+ , testCase "User.getWeeklyChartList" testGetWeeklyChartList+ , testCase "User.getWeeklyTrackChart" testGetWeeklyTrackChart+ ]+ where+ testGetArtistTracks = check gat $+ getArtistTracks <*> user "smpcln" <*> artist "Dvar" <*> ak++ testGetBannedTracks = check gbt $+ getBannedTracks <*> user "smpcln" <* limit 10 <*> ak++ testGetEvents = check ge $+ getEvents <*> user "chansonnier" <* limit 5 <*> ak++ testGetFriends = check gf $+ getFriends <*> user "smpcln" <* limit 10 <*> ak++ testGetPlayCount = check gpc $+ getInfo <*> user "smpcln" <*> ak++ testGetLovedTracks = check glt $+ getLovedTracks <*> user "smpcln" <* limit 10 <*> ak++ testGetNeighbours = check gn $+ getNeighbours <*> user "smpcln" <* limit 10 <*> ak++ testGetNewReleases = check gnr $+ getNewReleases <*> user "smpcln" <*> ak++ testGetPastEvents = check gpe $+ getPastEvents <*> user "mokele" <* limit 5 <*> ak++ testGetPersonalTags = check gpt $+ getPersonalTags <*> user "crackedcore" <*> tag "rhythmic noise" <*> taggingType "artist" <* limit 10 <*> ak++ testGetPlaylists = check gp $+ getPlaylists <*> user "mokele" <*> ak++ testGetRecentTracks = check grt $+ getRecentTracks <*> user "smpcln" <* limit 10 <*> ak++ testGetShouts = check gs $+ getShouts <*> user "smpcln" <* limit 2 <*> ak++ testGetTopAlbums = check gtal $+ getTopAlbums <*> user "smpcln" <* limit 5 <*> ak++ testGetTopArtists = check gtar $+ getTopArtists <*> user "smpcln" <* limit 5 <*> ak++ testGetTopTags = check gtta $+ getTopTags <*> user "smpcln" <* limit 10 <*> ak++ testGetTopTracks = check gttr $+ getTopTracks <*> user "smpcln" <* limit 10 <*> ak++ testGetWeeklyAlbumChart = check gwalc $+ getWeeklyAlbumChart <*> user "smpcln" <*> ak++ testGetWeeklyArtistChart = check gwarc $+ getWeeklyArtistChart <*> user "smpcln" <*> ak++ testGetWeeklyChartList = check gwcl $+ getWeeklyChartList <*> user "smpcln" <*> ak++ testGetWeeklyTrackChart = check gwtc $+ getWeeklyTrackChart <*> user "smpcln" <*> ak+++gpc ∷ Value → Parser String+gat, gbt, ge, gf, glt, gn, gnr, gp, gpe, gpt, gra, gre, grs, grt, gs, gtal, gtar, gtta, gttr, gwalc, gwarc, gwcl, gwtc ∷ Value → Parser [String]+gat o = parseJSON o >>= (.: "artisttracks") >>= (.: "track") >>= mapM (.: "name")+gbt o = parseJSON o >>= (.: "bannedtracks") >>= (.: "track") >>= mapM (.: "name")+ge o = parseJSON o >>= (.: "events") >>= (.: "event") >>= mapM (.: "venue") >>= mapM (.: "url")+gf o = parseJSON o >>= (.: "friends") >>= (.: "user") >>= mapM (.: "name")+glt o = parseJSON o >>= (.: "lovedtracks") >>= (.: "track") >>= mapM (.: "name")+gn o = parseJSON o >>= (.: "neighbours") >>= (.: "user") >>= mapM (.: "name")+gnr o = parseJSON o >>= (.: "albums") >>= (.: "album") >>= mapM (.: "url")+gp o = parseJSON o >>= (.: "playlists") >>= (.: "playlist") >>= mapM (.: "title")+gpc o = parseJSON o >>= (.: "user") >>= (.: "playcount")+gpe o = parseJSON o >>= (.: "events") >>= (.: "event") >>= mapM (.: "url")+gpt o = parseJSON o >>= (.: "taggings") >>= (.: "artists") >>= (.: "artist") >>= mapM (.: "name")+gra o = parseJSON o >>= (.: "recommendations") >>= (.: "artist") >>= mapM (.: "name")+gre o = parseJSON o >>= (.: "events") >>= (.: "event") >>= mapM (.: "url")+grs o = parseJSON o >>= (.: "recentstations") >>= (.: "station") >>= mapM (.: "name")+grt o = parseJSON o >>= (.: "recenttracks") >>= (.: "track") >>= mapM (.: "name")+gs o = parseJSON o >>= (.: "shouts") >>= (.: "shout") >>= mapM (.: "body")+gtal o = parseJSON o >>= (.: "topalbums") >>= (.: "album") >>= mapM (.: "artist") >>= mapM (.: "name")+gtar o = parseJSON o >>= (.: "topartists") >>= (.: "artist") >>= mapM (.: "name")+gtta o = parseJSON o >>= (.: "toptags") >>= (.: "tag") >>= mapM (.: "name")+gttr o = parseJSON o >>= (.: "toptracks") >>= (.: "track") >>= mapM (.: "url")+gwalc o = parseJSON o >>= (.: "weeklyalbumchart") >>= (.: "album") >>= mapM (.: "url")+gwarc o = parseJSON o >>= (.: "weeklyartistchart") >>= (.: "artist") >>= mapM (.: "url")+gwcl o = take 5 `fmap` (parseJSON o >>= (.: "weeklychartlist") >>= (.: "chart") >>= mapM (.: "from"))+gwtc o = parseJSON o >>= (.: "weeklytrackchart") >>= (.: "track") >>= mapM (.: "url")
+ tests/Venue.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UnicodeSyntax #-}+module Venue (noauth) where++import Data.Aeson.Types+import Network.Lastfm+import Network.Lastfm.Venue+import Test.Framework+import Test.Framework.Providers.HUnit++import Common+++noauth ∷ Request JSON Send APIKey → [Test]+noauth ak =+ [ testCase "Venue.getEvents" testGetEvents+ , testCase "Venue.getPastEvents" testGetPastEvents+ , testCase "Venue.search" testSearch+ ]+ where+ testGetEvents = check ge $+ getEvents <*> venue 9163107 <*> ak++ testGetPastEvents = check gpe $+ getPastEvents <*> venue 9163107 <* limit 2 <*> ak++ testSearch = check se $+ search <*> venueName "Arena" <*> ak+++ge, gpe, se ∷ Value → Parser [String]+ge o = parseJSON o >>= (.: "events") >>= (.: "event") >>= mapM (\t → (t .: "venue") >>= (.: "name"))+gpe o = parseJSON o >>= (.: "events") >>= (.: "event") >>= mapM (.: "title")+se o = parseJSON o >>= (.: "results") >>= (.: "venuematches") >>= (.: "venue") >>= mapM (.: "id")