grooveshark 0.2.11 → 0.2.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.rspec +2 -2
- data/.rubocop.yml +6 -0
- data/.travis.yml +11 -0
- data/Gemfile +2 -1
- data/README.md +5 -3
- data/Rakefile +11 -4
- data/grooveshark.gemspec +24 -17
- data/lib/grooveshark/broadcast.rb +5 -3
- data/lib/grooveshark/client.rb +67 -46
- data/lib/grooveshark/errors.rb +11 -4
- data/lib/grooveshark/playlist.rb +24 -20
- data/lib/grooveshark/song.rb +17 -15
- data/lib/grooveshark/user.rb +43 -27
- data/lib/grooveshark/utils.rb +14 -12
- data/lib/grooveshark/version.rb +2 -1
- data/spec/grooveshark/broadcast_spec.rb +41 -0
- data/spec/grooveshark/client_spec.rb +80 -0
- data/spec/grooveshark/errors_spec.rb +15 -0
- data/spec/grooveshark/playlist_spec.rb +98 -0
- data/spec/grooveshark/song_spec.rb +43 -0
- data/spec/grooveshark/user_spec.rb +233 -0
- data/spec/grooveshark/utils_spec.rb +38 -0
- data/spec/helper.rb +7 -2
- metadata +105 -39
- data/spec/broadcast_spec.rb +0 -39
- data/spec/client_spec.rb +0 -85
- data/spec/utils_spec.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 265e469716c245b3c4421d9b1f4a97e9808c3371
|
4
|
+
data.tar.gz: 4e1d82a8c9b0c9e3c12f40d200052ad4d7398c43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a9732cce0fdaf0edafd248efae89676f7003c1b4c6fa02fabbaadcb19d78cde55bb2fc69ab488abe124f5a3417f66cbec617b531782ca4177f8ce730b3a0a9f
|
7
|
+
data.tar.gz: b7d86f7a0ae7cc1170b3e62ad4bbadbaad2014472eca7c054abbcc56e5cb3db9f181e7e9d8b27ce1c9c66b230cf83243e96802ec2edf69b38bd44cf8e47f5448
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
--color
|
2
|
-
--format=
|
3
|
-
--backtrace
|
2
|
+
--format=documentation
|
3
|
+
--backtrace
|
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
# Grooveshark API
|
1
|
+
# Grooveshark API [![Build Status](https://travis-ci.org/sosedoff/grooveshark.svg?branch=master)](https://travis-ci.org/sosedoff/grooveshark)
|
2
2
|
|
3
3
|
Unofficial grooveshark API ruby library gives your ability to search and stream songs,
|
4
4
|
manage playlists, media library and favorites.
|
5
5
|
API was discovered using http proxy and does not pretend to be always valid due to website API changes.
|
6
6
|
|
7
|
+
**Looking for maintainer. Submit a new issue if interested.**
|
8
|
+
|
7
9
|
## Installation
|
8
10
|
|
9
11
|
Install gem from rubygems:
|
@@ -242,14 +244,14 @@ user.feed
|
|
242
244
|
Run test suite:
|
243
245
|
|
244
246
|
```
|
245
|
-
rake
|
247
|
+
bundle exec rake
|
246
248
|
```
|
247
249
|
|
248
250
|
## Contact
|
249
251
|
|
250
252
|
- Dan Sosedoff
|
251
253
|
- dan.sosedoff@gmail.com
|
252
|
-
- http://twitter.com/
|
254
|
+
- http://twitter.com/sosedoff
|
253
255
|
|
254
256
|
## License
|
255
257
|
|
data/Rakefile
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
require 'bundler/gem_tasks'
|
3
|
-
require
|
3
|
+
require 'rspec/core/rake_task'
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
begin
|
6
|
+
require 'rubocop/rake_task'
|
7
|
+
|
8
|
+
RuboCop::RakeTask.new(:rubocop)
|
9
|
+
rescue LoadError
|
10
|
+
puts 'Rubocop is needed to run this task.'
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
7
14
|
t.verbose = false
|
8
15
|
end
|
9
16
|
|
10
|
-
task :
|
17
|
+
task default: [:rubocop, :spec]
|
data/grooveshark.gemspec
CHANGED
@@ -1,25 +1,32 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
require File.expand_path(
|
2
|
+
require File.expand_path('../lib/grooveshark/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
|
-
s.name =
|
5
|
+
s.name = 'grooveshark'
|
6
6
|
s.version = Grooveshark::VERSION
|
7
|
-
s.description =
|
8
|
-
s.summary =
|
9
|
-
s.authors = [
|
10
|
-
s.email =
|
11
|
-
s.homepage =
|
12
|
-
s.license =
|
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
|
+
s.license = 'MIT'
|
13
13
|
|
14
14
|
s.files = `git ls-files`.split("\n")
|
15
15
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map
|
17
|
-
|
18
|
-
|
19
|
-
s.
|
20
|
-
|
21
|
-
|
22
|
-
s.add_runtime_dependency
|
23
|
-
s.add_runtime_dependency
|
24
|
-
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map do |f|
|
17
|
+
File.basename(f)
|
18
|
+
end
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
|
21
|
+
s.add_runtime_dependency 'json', '>= 1.4.6'
|
22
|
+
s.add_runtime_dependency 'rest-client', '>= 1.5.1'
|
23
|
+
s.add_runtime_dependency 'uuid', '~> 2.0'
|
24
|
+
|
25
|
+
s.add_development_dependency 'rake', '~>10.0'
|
26
|
+
s.add_development_dependency 'rack-test', '~>0.6'
|
27
|
+
s.add_development_dependency 'rspec', '~>3.0'
|
28
|
+
s.add_development_dependency 'simplecov', '~>0.9'
|
29
|
+
s.add_development_dependency 'fakefs', '~>0.5'
|
30
|
+
|
31
|
+
s.add_development_dependency 'rubocop', '~>0.25' if RUBY_VERSION != '1.9.2'
|
25
32
|
end
|
@@ -1,16 +1,18 @@
|
|
1
|
+
# Grooveshark module
|
1
2
|
module Grooveshark
|
3
|
+
# Broadcast class
|
2
4
|
class Broadcast
|
3
5
|
attr_reader :id, :user_ids
|
4
6
|
attr_reader :is_active, :is_playing
|
5
7
|
attr_reader :name, :usernames
|
6
8
|
attr_reader :active_song, :next_song
|
7
9
|
|
8
|
-
def initialize(client, broadcast_id=nil, data=nil)
|
10
|
+
def initialize(client, broadcast_id = nil, data = nil)
|
9
11
|
@client = client
|
10
12
|
|
11
13
|
if broadcast_id
|
12
14
|
@id = broadcast_id
|
13
|
-
reload_status
|
15
|
+
reload_status
|
14
16
|
elsif data
|
15
17
|
@id = data['broadcast_id'] || broadcast_id
|
16
18
|
@name = data['name']
|
@@ -28,7 +30,7 @@ module Grooveshark
|
|
28
30
|
def reload_status
|
29
31
|
initialize(
|
30
32
|
@client, nil,
|
31
|
-
@client.request('broadcastStatusPoll',
|
33
|
+
@client.request('broadcastStatusPoll', broadcastID: @id)
|
32
34
|
)
|
33
35
|
true
|
34
36
|
rescue
|
data/lib/grooveshark/client.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
# Grooveshark module
|
1
2
|
module Grooveshark
|
3
|
+
# Client class
|
2
4
|
class Client
|
3
5
|
attr_accessor :session, :comm_token
|
4
6
|
attr_reader :user, :comm_token_ttl, :country
|
@@ -6,60 +8,71 @@ module Grooveshark
|
|
6
8
|
def initialize(params = {})
|
7
9
|
@ttl = params[:ttl] || 120 # 2 minutes
|
8
10
|
@uuid = UUID.new.generate.upcase
|
9
|
-
|
11
|
+
token_data
|
10
12
|
end
|
11
13
|
|
12
14
|
# Authenticate user
|
13
15
|
def login(user, password)
|
14
|
-
data = request('authenticateUser',
|
16
|
+
data = request('authenticateUser',
|
17
|
+
{ username: user, password: password },
|
18
|
+
true)
|
15
19
|
@user = User.new(self, data)
|
16
|
-
|
17
|
-
|
20
|
+
fail InvalidAuthentication, 'Wrong username or password!' if @user.id == 0
|
21
|
+
|
22
|
+
@user
|
18
23
|
end
|
19
24
|
|
20
25
|
# Find user by ID
|
21
26
|
def get_user_by_id(id)
|
22
|
-
resp = request('getUserByID',
|
23
|
-
resp['
|
27
|
+
resp = request('getUserByID', userID: id)['user']
|
28
|
+
resp['user_id'].nil? ? nil : User.new(self, resp)
|
24
29
|
end
|
25
30
|
|
26
31
|
# Find user by username
|
27
32
|
def get_user_by_username(name)
|
28
|
-
resp = request('getUserByUsername',
|
29
|
-
resp['
|
33
|
+
resp = request('getUserByUsername', username: name)['user']
|
34
|
+
resp['user_id'].nil? ? nil : User.new(self, resp)
|
30
35
|
end
|
31
36
|
|
32
37
|
# Get recently active users
|
33
38
|
def recent_users
|
34
|
-
request('getRecentlyActiveUsers', {})['users']
|
39
|
+
request('getRecentlyActiveUsers', {})['users']
|
40
|
+
.map do |u|
|
41
|
+
User.new(self, u)
|
42
|
+
end
|
35
43
|
end
|
36
44
|
|
37
45
|
# Get popular songs
|
38
46
|
# type => daily, monthly
|
39
|
-
def popular_songs(type='daily')
|
40
|
-
|
41
|
-
request('popularGetSongs',
|
47
|
+
def popular_songs(type = 'daily')
|
48
|
+
fail ArgumentError, 'Invalid type' unless %w(daily monthly).include?(type)
|
49
|
+
request('popularGetSongs', type: type)['songs'].map { |s| Song.new(s) }
|
42
50
|
end
|
43
51
|
|
44
52
|
# Get top broadcasts
|
45
53
|
# count => specifies how many broadcasts to get
|
46
|
-
def top_broadcasts(count=10)
|
54
|
+
def top_broadcasts(count = 10)
|
47
55
|
top_broadcasts = []
|
48
|
-
request('getTopBroadcastsCombined').each do |key,
|
56
|
+
request('getTopBroadcastsCombined').each do |key, _val|
|
49
57
|
broadcast_id = key.split(':')[1]
|
50
58
|
top_broadcasts.push(Broadcast.new(self, broadcast_id))
|
51
59
|
count -= 1
|
52
|
-
if count == 0
|
53
|
-
break
|
54
|
-
end
|
60
|
+
break if count == 0
|
55
61
|
end
|
56
|
-
|
62
|
+
|
63
|
+
top_broadcasts
|
57
64
|
end
|
58
65
|
|
59
66
|
# Perform search request for query
|
60
67
|
def search(type, query)
|
61
|
-
results =
|
62
|
-
|
68
|
+
results = []
|
69
|
+
search = request('getResultsFromSearch', type: type, query: query)
|
70
|
+
results = search['result'].map do |data|
|
71
|
+
next Song.new data if type == 'Songs'
|
72
|
+
next Playlist.new data if type == 'Playlists'
|
73
|
+
data
|
74
|
+
end if search.key?('result')
|
75
|
+
results
|
63
76
|
end
|
64
77
|
|
65
78
|
# Perform songs search request for query
|
@@ -69,20 +82,20 @@ module Grooveshark
|
|
69
82
|
|
70
83
|
# Return raw response for songs search request
|
71
84
|
def search_songs_pure(query)
|
72
|
-
request('getSearchResultsEx',
|
85
|
+
request('getSearchResultsEx', type: 'Songs', query: query)
|
73
86
|
end
|
74
87
|
|
75
88
|
# Get stream authentication by song ID
|
76
89
|
def get_stream_auth_by_songid(song_id)
|
77
|
-
result = request('getStreamKeyFromSongIDEx',
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
90
|
+
result = request('getStreamKeyFromSongIDEx',
|
91
|
+
'type' => 0,
|
92
|
+
'prefetch' => false,
|
93
|
+
'songID' => song_id,
|
94
|
+
'country' => @country,
|
95
|
+
'mobile' => false)
|
96
|
+
if result == []
|
97
|
+
fail GeneralError, 'No data for this song. ' \
|
98
|
+
'Maybe Grooveshark banned your IP.'
|
86
99
|
end
|
87
100
|
result
|
88
101
|
end
|
@@ -103,16 +116,18 @@ module Grooveshark
|
|
103
116
|
get_song_url_by_id(song.id)
|
104
117
|
end
|
105
118
|
|
106
|
-
def
|
119
|
+
def token_data
|
107
120
|
response = RestClient.get('http://grooveshark.com')
|
108
121
|
|
109
|
-
preload_regex = /gsPreloadAjax\(\{url: '\/preload.php\?(.*)&hash=' \+ clientPage\}\)/
|
122
|
+
preload_regex = /gsPreloadAjax\(\{url: '\/preload.php\?(.*)&hash=' \+ clientPage\}\)/ # rubocop:disable Metrics/LineLength
|
110
123
|
preload_id = response.to_s.scan(preload_regex).flatten.first
|
111
|
-
preload_url = "http://grooveshark.com/preload.php?#{preload_id}
|
124
|
+
preload_url = "http://grooveshark.com/preload.php?#{preload_id}" \
|
125
|
+
'&getCommunicationToken=1&hash=%2F'
|
112
126
|
preload_response = RestClient.get(preload_url)
|
113
127
|
|
114
|
-
token_data_json = preload_response.to_s
|
115
|
-
|
128
|
+
token_data_json = preload_response.to_s
|
129
|
+
.scan(/window.tokenData = (.*);/).flatten.first
|
130
|
+
fail GeneralError, 'token data not found' unless token_data_json
|
116
131
|
token_data = JSON.parse(token_data_json)
|
117
132
|
@comm_token = token_data['getCommunicationToken']
|
118
133
|
@comm_token_ttl = Time.now.to_i
|
@@ -124,7 +139,7 @@ module Grooveshark
|
|
124
139
|
# Sign method
|
125
140
|
def create_token(method)
|
126
141
|
rnd = get_random_hex_chars(6)
|
127
|
-
salt =
|
142
|
+
salt = 'gooeyFlubber'
|
128
143
|
plain = [method, @comm_token, salt, rnd].join(':')
|
129
144
|
hash = Digest::SHA1.hexdigest(plain)
|
130
145
|
"#{rnd}#{hash}"
|
@@ -135,11 +150,7 @@ module Grooveshark
|
|
135
150
|
(0...length).map { chars[rand(chars.length)] }.join
|
136
151
|
end
|
137
152
|
|
138
|
-
|
139
|
-
def request(method, params={}, secure=false)
|
140
|
-
refresh_token if @comm_token
|
141
|
-
|
142
|
-
url = "#{secure ? 'https' : 'http'}://grooveshark.com/more.php?#{method}"
|
153
|
+
def body(method, params)
|
143
154
|
body = {
|
144
155
|
'header' => {
|
145
156
|
'client' => 'mobileshark',
|
@@ -153,17 +164,27 @@ module Grooveshark
|
|
153
164
|
'parameters' => params
|
154
165
|
}
|
155
166
|
body['header']['token'] = create_token(method) if @comm_token
|
167
|
+
body
|
168
|
+
end
|
169
|
+
|
170
|
+
# Perform API request
|
171
|
+
def request(method, params = {}, secure = false)
|
172
|
+
refresh_token if @comm_token
|
173
|
+
|
174
|
+
url = "#{secure ? 'https' : 'http'}://grooveshark.com/more.php?#{method}"
|
156
175
|
begin
|
157
|
-
data = RestClient.post(url,
|
158
|
-
|
176
|
+
data = RestClient.post(url,
|
177
|
+
body(method, params).to_json,
|
178
|
+
'Content-Type' => 'application/json')
|
179
|
+
rescue StandardError => ex
|
159
180
|
raise GeneralError, ex.message
|
160
181
|
end
|
161
182
|
|
162
183
|
data = JSON.parse(data)
|
163
|
-
data = data.normalize if data.
|
184
|
+
data = data.normalize if data.is_a?(Hash)
|
164
185
|
|
165
186
|
if data.key?('fault')
|
166
|
-
|
187
|
+
fail ApiError, data['fault']
|
167
188
|
else
|
168
189
|
data['result']
|
169
190
|
end
|
@@ -171,7 +192,7 @@ module Grooveshark
|
|
171
192
|
|
172
193
|
# Refresh communications token on ttl
|
173
194
|
def refresh_token
|
174
|
-
|
195
|
+
token_data if Time.now.to_i - @comm_token_ttl > @ttl
|
175
196
|
end
|
176
197
|
end
|
177
198
|
end
|
data/lib/grooveshark/errors.rb
CHANGED
@@ -1,8 +1,15 @@
|
|
1
|
+
# Grooveshark module
|
1
2
|
module Grooveshark
|
2
|
-
class InvalidAuthentication < Exception
|
3
|
-
|
4
|
-
|
3
|
+
class InvalidAuthentication < Exception
|
4
|
+
end
|
5
|
+
|
6
|
+
class ReadOnlyAccess < Exception
|
7
|
+
end
|
8
|
+
|
9
|
+
class GeneralError < Exception
|
10
|
+
end
|
5
11
|
|
12
|
+
# Api error
|
6
13
|
class ApiError < Exception
|
7
14
|
attr_reader :code
|
8
15
|
|
@@ -15,4 +22,4 @@ module Grooveshark
|
|
15
22
|
"#{@code} - #{@message}"
|
16
23
|
end
|
17
24
|
end
|
18
|
-
end
|
25
|
+
end
|
data/lib/grooveshark/playlist.rb
CHANGED
@@ -1,44 +1,48 @@
|
|
1
|
+
# Grooveshark module
|
1
2
|
module Grooveshark
|
3
|
+
# Playlist class
|
2
4
|
class Playlist
|
3
5
|
attr_reader :id, :user_id
|
4
6
|
attr_reader :name, :about, :picture, :username
|
5
7
|
attr_reader :songs
|
6
8
|
|
7
|
-
def initialize(client, data=nil, user_id=nil)
|
9
|
+
def initialize(client, data = nil, user_id = nil)
|
8
10
|
@client = client
|
9
11
|
@songs = []
|
10
12
|
|
11
|
-
if data
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
13
|
+
return if data.nil?
|
14
|
+
@id = data['playlist_id']
|
15
|
+
@name = data['name']
|
16
|
+
@about = data['about']
|
17
|
+
@picture = data['picture']
|
18
|
+
@user_id = data['user_id'] || user_id
|
19
|
+
@username = data['user_name']
|
19
20
|
end
|
20
21
|
|
21
22
|
# Fetch playlist songs
|
22
23
|
def load_songs
|
23
|
-
@songs =
|
24
|
-
@
|
24
|
+
@songs = []
|
25
|
+
playlist = @client.request('getPlaylistByID', playlistID: @id)
|
26
|
+
@songs = playlist['songs'].map! do |s|
|
27
|
+
Song.new(s)
|
28
|
+
end if playlist.key?('songs')
|
29
|
+
@songs
|
25
30
|
end
|
26
31
|
|
27
32
|
# Rename playlist
|
28
33
|
def rename(name, description)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
34
|
+
@client.request('renamePlaylist', playlistID: @id, playlistName: name)
|
35
|
+
@client.request('setPlaylistAbout', playlistID: @id, about: description)
|
36
|
+
@name = name
|
37
|
+
@about = description
|
38
|
+
true
|
39
|
+
rescue
|
40
|
+
false
|
37
41
|
end
|
38
42
|
|
39
43
|
# Delete existing playlist
|
40
44
|
def delete
|
41
|
-
@client.request('deletePlaylist',
|
45
|
+
@client.request('deletePlaylist', playlistID: @id, name: @name)
|
42
46
|
end
|
43
47
|
end
|
44
48
|
end
|