grooveshark 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,41 @@
1
+ !.gitignore
2
+ *.gem
3
+ *.rbc
4
+ *.sw[a-p]
5
+ *.tmproj
6
+ *.tmproject
7
+ *.un~
8
+ *~
9
+ .DS_Store
10
+ .Spotlight-V100
11
+ .Trashes
12
+ ._*
13
+ .bundle
14
+ .config
15
+ .directory
16
+ .elc
17
+ .redcar
18
+ .yardoc
19
+ /.emacs.desktop
20
+ /.emacs.desktop.lock
21
+ Desktop.ini
22
+ Gemfile.lock
23
+ Icon?
24
+ InstalledFiles
25
+ Session.vim
26
+ Thumbs.db
27
+ \#*\#
28
+ _yardoc
29
+ auto-save-list
30
+ coverage
31
+ doc/
32
+ lib/bundler/man
33
+ pkg
34
+ pkg/*
35
+ rdoc
36
+ spec/reports
37
+ test/tmp
38
+ test/version_tmp
39
+ tmp
40
+ tmtags
41
+ tramp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format=nested
3
+ --backtrace
data/README.rdoc ADDED
@@ -0,0 +1,159 @@
1
+ = Grooveshark API
2
+
3
+ Unofficial grooveshark API ruby library gives your ability to search and stream songs,
4
+ manage playlists, media library and favorites.
5
+ API was discovered using http proxy and does not pretend to be always valid due to website API changes.
6
+
7
+ = Installation
8
+
9
+ gem install grooveshark
10
+
11
+ = Getting Started
12
+
13
+ Lets first create a session.
14
+ Grooveshark session is a regular PHP session with expiration date of 7 days.
15
+
16
+ require 'rubygems'
17
+ require 'grooveshark'
18
+
19
+ client = Grooveshark::Client.new
20
+
21
+ To get session key just call
22
+
23
+ session = client.session
24
+
25
+ You can store this key for 7 days after creation and use it like this:
26
+
27
+ client = Grooveshark::Client.new(SESSION_KEY)
28
+
29
+ Now we can find some songs:
30
+
31
+ songs = client.search_songs('Nirvana')
32
+
33
+ songs.each do |s|
34
+ s.id # Song ID
35
+ s.name # Song name
36
+ s.artist # Song artist name
37
+ s.album # Song album name
38
+ s.duration # Song duration in seconds (not always present, 0 by default)
39
+ end
40
+
41
+ We got collection of songs. Check Song object for additional attributes.
42
+ In order to stream song we need to get the authorization
43
+
44
+ song = songs.first
45
+ url = client.get_song_url(song)
46
+
47
+ Given url is valid only for current session and cannot be shared or stored permanently.
48
+ Also, it probably violates terms of service.
49
+
50
+ = User Authentication
51
+
52
+ To get your user account you need to provide username and password.
53
+ If username or password is not valid InvalidAuthentication exception will be raised.
54
+
55
+ client = Grooveshark::Client.new
56
+
57
+ begin
58
+ user = client.login('username', 'password')
59
+ rescue InvalidAuthentication
60
+ puts "Oooops! Wrong username or password"
61
+ end
62
+
63
+ = Playlists and favorites
64
+
65
+ Get all user playlists
66
+
67
+ user.playlists.each do |p|
68
+ p.id # Playlist ID
69
+ p.name # Playlist name
70
+ p.about # Playlist description (empty by default)
71
+ end
72
+
73
+ Get user playlist
74
+
75
+ playlist = user.get_playlist(PLAYLIST_ID)
76
+
77
+ Get all playlist songs
78
+
79
+ playlist = user.get_playlist(ID)
80
+ playlist.load_songs
81
+ songs = playlist.songs
82
+
83
+ Rename existing playlist
84
+
85
+ playlist = user.get_playlist(ID)
86
+ playlist.rename('NEW NAME', 'NEW DESCRIPTION')
87
+
88
+ Delete existing user playlist
89
+
90
+ playlist = user.get_playlist(ID)
91
+ playlist.delete
92
+
93
+ Create a new playlist. First parameter is mandatory, description and songs are optional.
94
+ For songs you can provide array of Song objects or array of IDs.
95
+
96
+ songs = client.search_songs('Joe Satriani')
97
+ p = user.create_playlist('NAME', 'DESCRIPTION', songs)
98
+
99
+ Get user favorite songs
100
+
101
+ songs = user.favorites
102
+
103
+ Add song to favorites
104
+
105
+ user.add_favorite(song) # Song object or song ID
106
+
107
+ Remove song from favorites
108
+
109
+ user.remove_favorite(song) # Song object or song ID
110
+
111
+ = User library
112
+
113
+ Get all songs from library as a collection of Song objects
114
+
115
+ songs = user.library
116
+
117
+ Add songs to library
118
+
119
+ songs = client.search_songs('The Beatles')
120
+ user.library_add(songs)
121
+
122
+ Remove selected songs from library. Unfortunately mass-deletion is not supported by Grooveshark API. You will have to delete each song via separate method call.
123
+
124
+ song = user.library.first # Lest pick a first song in the library
125
+ user.library_remove(song)
126
+
127
+ = Explore community
128
+
129
+ Get all recently active users
130
+
131
+ client.recent_users
132
+
133
+ Find user by ID
134
+
135
+ client.get_user_by_id('ID')
136
+
137
+ Find user by username
138
+
139
+ client.get_user_by_username('username')
140
+
141
+ Fetch recent user activity
142
+
143
+ user = client.get_user_by_username('user')
144
+ user.feed
145
+
146
+ = Known issues
147
+
148
+ * Communication token gets rejected after some time. This timeframe is always different. Additional research didnt show any results.
149
+
150
+ = TODO
151
+
152
+ * Testing
153
+ * Library management coverage
154
+ * More methods
155
+
156
+ = Credits
157
+
158
+ * Dan Sosedoff - http://github.com/sosedoff
159
+ * Daniel Lamando - http://github.com/danopia
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ RSpec::Core::RakeTask.new(:test) do |spec|
4
+ spec.pattern = 'spec/*_spec.rb'
5
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/grooveshark/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "grooveshark"
6
+ s.version = Grooveshark::VERSION.dup
7
+ s.description = "Unofficial ruby library for consuming the Grooveshark API."
8
+ s.summary = "Grooveshark API"
9
+ s.authors = ["Dan Sosedoff"]
10
+ s.email = "dan.sosedoff@gmail.com"
11
+ s.homepage = "http://github.com/sosedoff/grooveshark"
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
16
+ s.require_paths = ['lib']
17
+
18
+ s.add_development_dependency 'rspec', '~> 2.6'
19
+
20
+ s.add_runtime_dependency 'json', '>= 1.4.6'
21
+ s.add_runtime_dependency 'rest-client', '>= 1.5.1'
22
+ end
@@ -4,12 +4,20 @@ module Grooveshark
4
4
 
5
5
  attr_accessor :session, :comm_token
6
6
  attr_reader :user
7
+ attr_reader :comm_token_ttl
8
+
9
+ SALT = 'backToTheScienceLab'
10
+ METHOD_SALTS = {
11
+ 'getStreamKeyFromSongIDEx' => 'bewareOfBearsharktopus'
12
+ }
7
13
 
8
14
  def initialize(session=nil)
9
15
  @session = session || get_session
10
16
  get_comm_token
11
17
  end
12
18
 
19
+ protected
20
+
13
21
  # Obtain new session from Grooveshark
14
22
  def get_session
15
23
  resp = RestClient.get('http://listen.grooveshark.com')
@@ -18,18 +26,22 @@ module Grooveshark
18
26
 
19
27
  # Get communication token
20
28
  def get_comm_token
21
- @comm_token = nil # so that it doesn't send a token
29
+ @comm_token = nil
22
30
  @comm_token = request('getCommunicationToken', {:secretKey => Digest::MD5.hexdigest(@session)}, true)
31
+ @comm_token_ttl = Time.now.to_i
23
32
  end
24
33
 
25
34
  # Sign method
26
35
  def create_token(method)
27
36
  rnd = rand(256**3).to_s(16).rjust(6, '0')
28
- plain = [method, @comm_token, 'quitStealinMahShit', rnd].join(':')
37
+ salt = METHOD_SALTS.key?(method) ? METHOD_SALTS[method] : SALT
38
+ plain = [method, @comm_token, salt, rnd].join(':')
29
39
  hash = Digest::SHA1.hexdigest(plain)
30
40
  "#{rnd}#{hash}"
31
41
  end
32
42
 
43
+ public
44
+
33
45
  # Authenticate user
34
46
  def login(user, password)
35
47
  data = request('authenticateUser', {:username => user, :password => password}, true)
@@ -54,6 +66,13 @@ module Grooveshark
54
66
  def recent_users
55
67
  request('getRecentlyActiveUsers', {})['users'].map { |u| User.new(self, u) }
56
68
  end
69
+
70
+ # Get popular songs
71
+ # type => daily, monthly
72
+ def popular_songs(type='daily')
73
+ raise ArgumentError, 'Invalid type' unless ['daily', 'monthly'].include?(type)
74
+ request('popularGetSongs', {:type => type})['songs'].map { |s| Song.new(s) }
75
+ end
57
76
 
58
77
  # Perform search request for query
59
78
  def search(type, query)
@@ -2,4 +2,17 @@ module Grooveshark
2
2
  class InvalidAuthentication < Exception ; end
3
3
  class ReadOnlyAccess < Exception ; end
4
4
  class GeneralError < Exception ; end
5
+
6
+ class ApiError < Exception
7
+ attr_reader :code
8
+
9
+ def initialize(fault)
10
+ @code = fault['code']
11
+ @message = fault['message']
12
+ end
13
+
14
+ def to_s
15
+ "#{@code} - #{@message}"
16
+ end
17
+ end
5
18
  end
@@ -1,10 +1,11 @@
1
1
  module Grooveshark
2
2
  module Request
3
- API_BASE = 'cowbell.grooveshark.com'
4
- UUID = 'E2AB1A59-C6B7-480E-992A-55DE1699D7F8'
5
- CLIENT = 'htmlshark'
6
- CLIENT_REV = '20101012.37'
7
- COUNTRY = {"CC2" => "0","IPR" => "1","CC1" => "0","ID" => "1","CC4" => "0","CC3" => "0"}
3
+ API_BASE = 'cowbell.grooveshark.com'
4
+ UUID = 'A3B724BA-14F5-4932-98B8-8D375F85F266'
5
+ CLIENT = 'htmlshark'
6
+ CLIENT_REV = '20110606.04'
7
+ COUNTRY = {"CC2" => "0", "IPR" => "353", "CC4" => "1073741824", "CC3" => "0", "CC1" => "0", "ID" => "223"}
8
+ TOKEN_TTL = 120 # 2 minutes
8
9
 
9
10
  # Client overrides for different methods
10
11
  METHOD_CLIENTS = {
@@ -13,6 +14,8 @@ module Grooveshark
13
14
 
14
15
  # Perform API request
15
16
  def request(method, params={}, secure=false)
17
+ refresh_token if @comm_token
18
+
16
19
  agent = METHOD_CLIENTS.key?(method) ? METHOD_CLIENTS[method] : CLIENT
17
20
  url = "#{secure ? 'https' : 'http'}://#{API_BASE}/more.php?#{method}"
18
21
  body = {
@@ -41,7 +44,17 @@ module Grooveshark
41
44
 
42
45
  data = JSON.parse(data)
43
46
  data = data.normalize if data.kind_of?(Hash)
44
- return data['result'] unless data['fault']
47
+
48
+ if data.key?('fault')
49
+ raise ApiError.new(data['fault'])
50
+ else
51
+ data['result']
52
+ end
53
+ end
54
+
55
+ # Refresh communications token on ttl
56
+ def refresh_token
57
+ get_comm_token if Time.now.to_i - @comm_token_ttl > TOKEN_TTL
45
58
  end
46
59
  end
47
60
  end
@@ -2,8 +2,8 @@ module Grooveshark
2
2
  class Song
3
3
  attr_reader :data
4
4
  attr_reader :id, :artist_id, :album_id
5
- attr_reader :name, :artist, :album, :track
6
- attr_reader :duraion, :artwork, :playcount
5
+ attr_reader :name, :artist, :album, :track, :year
6
+ attr_reader :duration, :artwork, :playcount
7
7
 
8
8
  def initialize(data=nil)
9
9
  unless data.nil?
@@ -18,6 +18,7 @@ module Grooveshark
18
18
  @duration = data['estimate_duration']
19
19
  @artwork = data['cover_art_filename']
20
20
  @playcount = data['song_plays']
21
+ @year = data['year']
21
22
  end
22
23
  end
23
24
 
@@ -52,6 +52,11 @@ module Grooveshark
52
52
  @client.request('userRemoveSongFromLibrary', req)
53
53
  end
54
54
 
55
+ # Get library modification time
56
+ def library_ts_modified
57
+ @client.request('userGetLibraryTSModified', {:userID => @id})
58
+ end
59
+
55
60
  # --------------------------------------------------------------------------
56
61
  # User Playlists
57
62
  # --------------------------------------------------------------------------
@@ -69,6 +74,8 @@ module Grooveshark
69
74
  result.nil? ? nil : result.first
70
75
  end
71
76
 
77
+ alias :playlist :get_playlist
78
+
72
79
  # Create new user playlist
73
80
  def create_playlist(name, description='', songs=[])
74
81
  @client.request('createPlaylist', {
@@ -1,3 +1,3 @@
1
1
  module Grooveshark
2
- VERSION = '0.2.1'
2
+ VERSION = '0.2.2'
3
3
  end
@@ -0,0 +1,36 @@
1
+ require File.expand_path("./helper", File.dirname(__FILE__))
2
+
3
+ describe 'Client' do
4
+ context 'initialization' do
5
+ it 'should have a valid session' do
6
+ @gs = Grooveshark::Client.new
7
+ @gs.session.should_not == nil
8
+ @gs.session.should match /^[abcdef\d]{32}$/i
9
+ end
10
+ end
11
+
12
+ context 'authentication' do
13
+ it 'should raise InvalidAuthentication error for invalid credentials' do
14
+ @gs = Grooveshark::Client.new
15
+ lambda { @gs.login('invlid_user_name', 'invalid_password') }.should raise_error Grooveshark::InvalidAuthentication
16
+ end
17
+ end
18
+
19
+ context 'search' do
20
+ before(:all) do
21
+ @gs = Grooveshark::Client.new
22
+ end
23
+
24
+ it 'should return empty songs collection' do
25
+ songs = @gs.search_songs("@@@@@%%%%%%%@%@%%@")
26
+ songs.should be_a_kind_of Array
27
+ songs.size.should == 0
28
+ end
29
+
30
+ it 'should return songs collection' do
31
+ songs = @gs.search_songs('Nirvana')
32
+ songs.should be_a_kind_of Array
33
+ songs.size.should_not == 0
34
+ end
35
+ end
36
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,3 @@
1
+ $:.unshift File.expand_path("../..", __FILE__)
2
+
3
+ require 'grooveshark'
@@ -0,0 +1,23 @@
1
+ require File.expand_path("./helper", File.dirname(__FILE__))
2
+
3
+ describe 'Request' do
4
+ module Grooveshark
5
+ module Request
6
+ TOKEN_TTL = 1 # override default ttl for tests
7
+ end
8
+ end
9
+
10
+ it 'should obtain a new communication token on TTL expiration' do
11
+ @gs = Grooveshark::Client.new
12
+ @tokens = []
13
+
14
+ 3.times do |i|
15
+ @gs.search_songs('Muse')
16
+ @tokens << @gs.comm_token
17
+ sleep 3
18
+ end
19
+
20
+ @tokens.uniq!
21
+ @tokens.size.should == 3
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ require File.expand_path("./helper", File.dirname(__FILE__))
2
+
3
+ describe 'String' do
4
+ it 'should normalize attributes' do
5
+ vars = ['key_name', 'keyName', 'KeyName', 'KeyNAME']
6
+ target = 'key_name'
7
+ vars.each { |s| s.normalize_attribute.should == target }
8
+ end
9
+ end
10
+
11
+ describe 'Hash' do
12
+ it 'should normalize simple keys' do
13
+ h = {'KeyName' => 'Value'}.normalize
14
+
15
+ h.key?('KeyName').should == false
16
+ h.key?('key_name').should == true
17
+ end
18
+
19
+ it 'should normalize symbol keys' do
20
+ h = {:KeyName => 'Value'}.normalize
21
+ h.key?(:KeyName).should == false
22
+ h.key?('key_name').should == true
23
+ end
24
+
25
+ it 'should normalize nested data' do
26
+ h = {
27
+ 'keyA' => {'nestedKey' => 'Value'},
28
+ 'keyB' => [{'arrKey' => 'Value'}]
29
+ }.normalize
30
+
31
+ h['key_a'].key?('nested_key').should == true
32
+ h['key_b'].class.should == Array
33
+ h['key_b'].first.key?('arr_key').should == true
34
+ end
35
+ end
metadata CHANGED
@@ -1,13 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grooveshark
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
5
4
  prerelease:
6
- segments:
7
- - 0
8
- - 2
9
- - 1
10
- version: 0.2.1
5
+ version: 0.2.2
11
6
  platform: ruby
12
7
  authors:
13
8
  - Dan Sosedoff
@@ -15,41 +10,42 @@ autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
12
 
18
- date: 2011-01-26 00:00:00 -06:00
13
+ date: 2011-08-01 00:00:00 -05:00
19
14
  default_executable:
20
15
  dependencies:
21
16
  - !ruby/object:Gem::Dependency
22
- name: json
17
+ name: rspec
23
18
  prerelease: false
24
19
  requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: "2.6"
25
+ type: :development
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
25
31
  none: false
26
32
  requirements:
27
33
  - - ">="
28
34
  - !ruby/object:Gem::Version
29
- hash: 11
30
- segments:
31
- - 1
32
- - 4
33
- - 6
34
35
  version: 1.4.6
35
36
  type: :runtime
36
- version_requirements: *id001
37
+ version_requirements: *id002
37
38
  - !ruby/object:Gem::Dependency
38
39
  name: rest-client
39
40
  prerelease: false
40
- requirement: &id002 !ruby/object:Gem::Requirement
41
+ requirement: &id003 !ruby/object:Gem::Requirement
41
42
  none: false
42
43
  requirements:
43
44
  - - ">="
44
45
  - !ruby/object:Gem::Version
45
- hash: 1
46
- segments:
47
- - 1
48
- - 5
49
- - 1
50
46
  version: 1.5.1
51
47
  type: :runtime
52
- version_requirements: *id002
48
+ version_requirements: *id003
53
49
  description: Unofficial ruby library for consuming the Grooveshark API.
54
50
  email: dan.sosedoff@gmail.com
55
51
  executables: []
@@ -59,15 +55,24 @@ extensions: []
59
55
  extra_rdoc_files: []
60
56
 
61
57
  files:
58
+ - .gitignore
59
+ - .rspec
60
+ - README.rdoc
61
+ - Rakefile
62
+ - grooveshark.gemspec
62
63
  - lib/grooveshark.rb
63
- - lib/grooveshark/version.rb
64
- - lib/grooveshark/utils.rb
65
64
  - lib/grooveshark/client.rb
66
65
  - lib/grooveshark/errors.rb
67
66
  - lib/grooveshark/playlist.rb
68
67
  - lib/grooveshark/request.rb
69
68
  - lib/grooveshark/song.rb
70
69
  - lib/grooveshark/user.rb
70
+ - lib/grooveshark/utils.rb
71
+ - lib/grooveshark/version.rb
72
+ - spec/client_spec.rb
73
+ - spec/helper.rb
74
+ - spec/request_spec.rb
75
+ - spec/utils_spec.rb
71
76
  has_rdoc: true
72
77
  homepage: http://github.com/sosedoff/grooveshark
73
78
  licenses: []
@@ -82,25 +87,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
87
  requirements:
83
88
  - - ">="
84
89
  - !ruby/object:Gem::Version
85
- hash: 3
86
- segments:
87
- - 0
88
90
  version: "0"
89
91
  required_rubygems_version: !ruby/object:Gem::Requirement
90
92
  none: false
91
93
  requirements:
92
94
  - - ">="
93
95
  - !ruby/object:Gem::Version
94
- hash: 3
95
- segments:
96
- - 0
97
96
  version: "0"
98
97
  requirements: []
99
98
 
100
99
  rubyforge_project:
101
- rubygems_version: 1.4.1
100
+ rubygems_version: 1.6.2
102
101
  signing_key:
103
102
  specification_version: 3
104
103
  summary: Grooveshark API
105
- test_files: []
106
-
104
+ test_files:
105
+ - spec/client_spec.rb
106
+ - spec/helper.rb
107
+ - spec/request_spec.rb
108
+ - spec/utils_spec.rb