Commits

Edoardo Tosca committed 2526041

first partial implementation of Grooveshark API in Clojure

Comments (0)

Files changed (13)

+# clj-grooveshark
+
+A Clojure library to provide bindings to Grooveshark API.
+
+## Usage
+
+FIXME
+
+## License
+
+Copyright © 2013 Edoardo Tosca
+
+Distributed under the Eclipse Public License, the same as Clojure.
+# Introduction to clj-grooveshark
+
+TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/)
+(defproject clj-grooveshark "0.1.0-SNAPSHOT"
+  :description "Clojure bindings for Grooveshark API"
+  :url "http://example.com/FIXME"
+  :license {:name "Eclipse Public License"
+            :url "http://www.eclipse.org/legal/epl-v10.html"}
+  :dependencies [[org.clojure/clojure "1.5.1"]
+                 [clj-http "0.7.0"]
+                 [org.clojure/data.json "0.2.1"]
+                 [digest "1.4.3"]])

src/clj_grooveshark/album.clj

+(ns clj-grooveshark.album
+  (:use clj-grooveshark.core))
+
+(defn get-album-search-results [query & [limit page]]
+  "Search for an album name given a query. Optionally a limit and a page number can be specified."
+  (let [query-params (into {} (filter second {:query query :limit limit :page page}))]
+    (execute (a-query "getAlbumSearchResults" query-params))
+    )
+  )
+
+(defn get-albums-info [album-id & more]
+  "Returns information for the given list of album ids."
+  (execute (a-query "getAlbumsInfo" {:albumIDs (concat (list album-id) more)}))
+  )
+
+(defn get-album-songs [album-id & [limit]]
+  "Returns songs for the given album id."
+  (let [query-params (into {} (filter second {:albumID album-id :limit limit }))]
+    (execute (a-query "getAlbumSongs" query-params))
+    )
+  )
+
+(defn get-does-album-exists? [album-id]
+  "Checks if a given album id exists"
+  (let [response (execute (a-query "getDoesAlbumExist", {:albumID album-id}))]
+    (get response "result")
+    )
+  )

src/clj_grooveshark/artist.clj

+(ns clj-grooveshark.artist
+  (:use clj-grooveshark.core)
+  )
+
+(defn get-artist-search-results
+  "Search for an artist given a query. A limit and a page number can be specified optionally"
+  [query & [limit page]]
+  (let [query-params (into {} (filter second {:query query :limit limit :page page}))]
+    (execute (a-query "getArtistSearchResults" query-params))
+    )
+  )
+
+(defn get-artist-albums
+  "Retrieve albums of an artist, given an artist id"
+  [artist-id]
+  (let [response (execute (a-query "getArtistAlbums" {:artistID artist-id}))]
+    (if (contains? response "errors")
+      (get response "errors")
+      (get (get response "result") "albums")
+      )
+    )
+  )
+
+(defn get-artist-popular-songs
+  "Retrieves artists most popular songs"
+  [artist-id]
+  (execute (a-query "getArtistPopularSongs", {:artistID artist-id}))
+  )
+
+(defn get-artist-verified-albums
+  "Retrieves artist's verified album given an artist id"
+  [artist-id]
+  (execute (a-query "getArtistVerifiedAlbums", {:artistID artist-id}))
+  )
+
+(defn get-does-artist-exists?
+  "Checks if a given artist id exists"
+  [artist-id]
+  (let [response (execute (a-query "getDoesArtistExist", {:artistID artist-id}))]
+    (get response "result")
+    )
+  )
+
+(defn get-artists-info
+  "Returns information for the given list of artist ids."
+  [artist-id & more]
+  (execute (a-query "getArtistsInfo" {:artistIDs  (concat (list artist-id) more)}))
+  )
+
+

src/clj_grooveshark/basic.clj

+(ns clj-grooveshark.basic
+  (:use clj-grooveshark.core)
+  (:require [clojure.data.json :as json])
+  )
+
+
+(defn ping
+  "Ping Grooveshark"
+  []
+  (let [response (execute-basic (a-query "pingService")) ]
+    (if (contains? (json/read-str (:body response)) "errors")
+      (println "errors")
+      (println "ok")
+      )
+    )
+  )
+
+
+(defn get-service-description []
+  (execute (a-query "getServiceDescription"))
+  )
+
+(defn get-country  []
+  (execute (a-query "getCountry"))
+  )
+
+

src/clj_grooveshark/configuration.clj

+(ns clj-grooveshark.configuration
+  )
+
+(def configuration
+  {:url "http://api.grooveshark.com/ws3.php"
+   :secure-url "https://api.grooveshark.com/ws3.php"
+   :key "A-KEY"
+   :secret "A-SECRET"
+   })
+
+
+
+
+

src/clj_grooveshark/core.clj

+(ns clj-grooveshark.core
+  (:require [clj-http.client :as client])
+  (:require [clojure.data.json :as json])
+  (:use clj-grooveshark.crypto)
+  (:use clj-grooveshark.configuration)
+  )
+
+(defn a-query
+  ([method] (json/write-str {:method method :header {:wsKey (:key configuration)}}))
+  ([method parameters] (json/write-str {:method method :parameters parameters :header {:wsKey (:key configuration)}}))
+  ([method session-id parameters] (json/write-str {:method method :parameters parameters :header {:wsKey (:key configuration) :sessionID session-id}}))
+  )
+
+(defn a-query2
+  "Creates a query with a given method name. Optionally can receive a map of body parameters and a map of header parameters."
+  [method & [parameters header]]
+  (let [query-params (into {} (filter second {:parameters parameters :header header}))]
+    (json/write-str (assoc query-params :method method :header (assoc (get query-params :header ) :wsKey (:key configuration))))
+    )
+  )
+
+;TO REFACTOR!
+(defn execute-basic [query]
+  (client/get (:url configuration) {:body query} {:as :json})
+  )
+
+(defn execute [query]
+  (json/read-str (:body (client/get (str (:url configuration) "?sig=" (toHexString (sign (:secret configuration) query))) {:body query} {:as :json})))
+  )
+
+(defn execute-secure [query]
+;  (json/read-str (:body (client/get (str (:secure-url configuration) "?sig=" (toHexString (sign (:secret configuration) query))) {:body query} {:as :json})))
+  (json/read-str (:body (client/get (str (:secure-url configuration) "?sig=" (toHexString (sign (:secret configuration) query))) {:body query} {:as :json})))
+  )
+
+
+
+

src/clj_grooveshark/crypto.clj

+(ns clj-grooveshark.crypto
+  (:import (javax.crypto Mac)
+           (javax.crypto.spec SecretKeySpec)))
+
+(defn secretKeyInst [key mac]
+  (SecretKeySpec. (.getBytes key) (.getAlgorithm mac)))
+
+(defn sign
+  "Returns the signature of a string with a given
+    key, using a MD5 HMAC."
+  [key string]
+  (let [mac (Mac/getInstance "HMacMD5")
+        secretKey (secretKeyInst key mac)]
+    (-> (doto mac
+          (.init secretKey)
+          (.update (.getBytes string)))
+      .doFinal)))
+
+
+
+(defn toString [byte]
+  (let [character (format "%x" byte)]
+    (if (= (count character) 1)
+      (str 0 character)
+      character
+      )
+    )
+  )
+
+(defn toHexString
+  "Convert bytes to a String"
+  [bytes]
+  (apply str (map toString bytes)))
+
+(defn sign-as-string [key string]
+  (toHexString (sign key string))
+  )

src/clj_grooveshark/playlist.clj

+(ns clj-grooveshark.playlist
+  (:use clj-grooveshark.core))
+
+(defn get-playlist
+  "Get playlist info and songs"
+  [playlist-id & [limit]]
+  (let [query-params (into {} (filter second {:playlistID playlist-id :limit limit}))]
+    (execute (a-query2 "getPlaylist" query-params nil))
+    )
+  )
+
+(defn get-playlist-info
+  "Get playlist information. To get songs use get-playlist"
+  [playlist-id]
+  (execute (a-query2 "getPlaylistInfo" {:playlistID playlist-id} nil))
+  )
+
+(defn get-playlist-songs
+  "Get playlist songs. Use get-playlist"
+  [playlist-id & [limit]]
+  (let [query-params (into {} (filter second {:playlistID playlist-id :limit limit}))]
+    (execute (a-query2 "getPlaylistSongs" query-params nil))
+    )
+  )
+
+(defn create-playlist
+  "Create a new playlist, optionally adding songs to it"
+  [playlist-name session-id song-id & more]
+  (execute-secure (a-query2 "createPlaylist" {:name playlist-name :songIDs (concat (list song-id) more)} {:sessionID session-id}))
+  )
+
+(defn set-playlist-songs
+  "Set playlist songs, overwrites any already saved"
+  [playlist-id session-id song-id & more]
+  (execute-secure (a-query2 "setPlaylistSongs" {:playlistID playlist-id :songIDs (concat (list song-id) more)} {:sessionID session-id}))
+  )
+
+(defn rename-playlist
+  "Renames a playlist"
+  [playlist-id playlist-name session-id]
+  (execute-secure (a-query2 "renamePlaylist" {:playlistID playlist-id :name playlist-name} {:sessionID session-id}))
+  )
+
+(defn delete-playlist
+  "Deletes a playlist"
+  [playlist-id session-id]
+  (execute-secure (a-query2 "deletePlaylist" {:playlistID playlist-id} {:sessionID session-id}))
+  )
+
+;DOES NOT WORK. ALWAYS RETURN FALSE
+(defn undelete-playlist
+  "Undeletes a playlist. Does not work!"
+  [playlist-id session-id]
+  (execute-secure (a-query2 "undeletePlaylist" {:playlistID playlist-id} {:sessionID session-id}))
+  )
+

src/clj_grooveshark/song.clj

+(ns clj-grooveshark.song
+  (:use clj-grooveshark.core))
+
+;(defn get-song-search-results)
+
+(defn get-does-song-exists?
+  "Checks if a given song id exists"
+  [song-id]
+  (let [response (execute (a-query "getDoesSongExist", {:songID song-id}))]
+    (get response "result")
+    )
+  )
+
+(defn get-songs-info
+  "Returns information for the given list of song ids."
+  [song-id & more]
+  (execute (a-query "getSongsInfo" {:artistIDs  (concat (list song-id) more)}))
+  )
+
+(defn get-popular-songs-today
+  "Get a subset of today's popular songs, from the Grooveshark popular billboard."
+  ([] (execute (a-query2 "getPopularSongsToday" nil nil)))
+  ([limit] (execute (a-query2 "getPopularSongsToday" {:limit  limit} nil)))
+
+  )

src/clj_grooveshark/user.clj

+(ns clj-grooveshark.user
+  (:require [digest])
+  (:use clj-grooveshark.core))
+
+(defn get-user-id-from-username
+  "Returns the userId given a valid username. If the username does not exits returns UserID=0"
+  [username]
+  (execute (a-query2 "getUserIDFromUsername" {:username username} nil))
+  )
+
+(defn start-session
+  "Start a new session"
+  []
+  (execute-secure (a-query "startSession"))
+  )
+
+(defn logout
+  "Logout a user from a session"
+  [session-id]
+  (execute-secure (a-query2 "logout" nil {:sessionID session-id}))
+  )
+
+(defn authenticate
+  "Authenticate a user given a username, a password and a previously created sessionId"
+  [username password session-id]
+  (execute-secure (a-query2 "authenticate" {:login (.toLowerCase username) :password (digest/md5 password)} {:sessionID session-id}))
+  )
+
+;(defn addUserLibrarySongs [song-ids album-ids artist-ids session-id]
+;  )
+
+
+(defn get-user-info
+  "Returns user information for a given the sessionId. Requires an authenticated session"
+  [session-id]
+  (execute-secure (a-query2 "getUserInfo" nil {:sessionID session-id}))
+  )
+
+(defn get-user-subscription-details
+  "Get logged-in user subscription info. Returns type of subscription and either dateEnd or recurring."
+  [session-id]
+  (execute-secure (a-query2 "getUserSubscriptionDetails" nil {:sessionID session-id}))
+  )
+
+;(defn get-user-favourite-songs [session-id & [limit]]
+;  "Get user favorite songs. Requires an authenticated session"
+;  (if (nil? limit))
+;
+;  (let [query-params (into {} (filter second {:limit limit}))]
+;    (execute-secure (a-query2 "getUserFavoriteSongs" (empty?) {:sessionID session-id}))
+;    )
+;  )
+
+

test/clj_grooveshark/core_test.clj

+(ns clj-grooveshark.core-test
+  (:require [clojure.data.json :as json])
+  (:use clojure.test
+        clj-grooveshark.core))
+
+
+(defn jsonify [query]
+  (json/read-str query)
+  )
+
+
+(deftest should-create-a-basic-query
+  (testing "a basic query"
+    (is (= {"method" "pingService", "header" {"wsKey" "A-KEY"}} (jsonify (a-query2 "pingService"))))
+    )
+  )
+
+(deftest should-create-a-query-with-parameters
+  (testing "a query with parameters"
+    (is (= {"method" "getDoesAlbumExist", "parameters" {"albumID" "1234"}, "header" {"wsKey" "A-KEY"}} (jsonify (a-query2 "getDoesAlbumExist", {:albumID "1234"}))))
+    )
+  )
+
+(deftest should-create-a-query-with-extra-headers
+  (testing "a query with extra headers"
+    (is (= {"method" "logout", "header" {"wsKey" "A-KEY", "sessionID" "1234abcd"}} (jsonify (a-query2 "logout" nil , {:sessionID "1234abcd"}))))
+    )
+  )
+
+