grooveshark 0.2.3 → 0.2.4
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/Gemfile +2 -0
- data/Rakefile +2 -0
- data/lib/grooveshark.rb +2 -1
- data/lib/grooveshark/client.rb +38 -24
- data/lib/grooveshark/request.rb +39 -28
- data/lib/grooveshark/version.rb +1 -1
- data/spec/client_spec.rb +35 -1
- data/spec/request_spec.rb +17 -15
- metadata +11 -9
data/Gemfile
ADDED
data/Rakefile
CHANGED
data/lib/grooveshark.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'digest'
|
2
2
|
require 'json'
|
3
3
|
require 'rest-client'
|
4
|
+
require 'uuid'
|
4
5
|
|
5
6
|
require 'grooveshark/utils'
|
6
7
|
require 'grooveshark/errors'
|
@@ -8,4 +9,4 @@ require 'grooveshark/request'
|
|
8
9
|
require 'grooveshark/client'
|
9
10
|
require 'grooveshark/user'
|
10
11
|
require 'grooveshark/playlist'
|
11
|
-
require 'grooveshark/song'
|
12
|
+
require 'grooveshark/song'
|
data/lib/grooveshark/client.rb
CHANGED
@@ -3,42 +3,45 @@ module Grooveshark
|
|
3
3
|
include Grooveshark::Request
|
4
4
|
|
5
5
|
attr_accessor :session, :comm_token
|
6
|
-
attr_reader :user
|
7
|
-
attr_reader :comm_token_ttl
|
8
|
-
|
9
|
-
SALT = 'imOnAHorse'
|
10
|
-
METHOD_SALTS = {
|
11
|
-
'getStreamKeyFromSongIDEx' => 'theTicketsAreNowDiamonds'
|
12
|
-
}
|
6
|
+
attr_reader :user, :comm_token_ttl, :country
|
13
7
|
|
14
8
|
def initialize(session=nil)
|
15
|
-
@session =
|
9
|
+
@session, @country = get_session_and_country
|
10
|
+
@uuid = UUID.new.generate.upcase
|
16
11
|
get_comm_token
|
17
12
|
end
|
18
13
|
|
19
14
|
protected
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
15
|
+
|
16
|
+
def get_session_and_country
|
17
|
+
response = RestClient.get('http://grooveshark.com')
|
18
|
+
session = response.headers[:set_cookie].to_s.scan(/PHPSESSID=([a-z\d]{32});/i).flatten.first
|
19
|
+
config_json = response.to_s.scan(/window.gsConfig = ({.*?});/).flatten.first
|
20
|
+
raise GeneralError, "gsConfig not found" if not config_json
|
21
|
+
config = JSON.parse(config_json)
|
22
|
+
[session, config['country']]
|
25
23
|
end
|
26
24
|
|
27
25
|
# Get communication token
|
28
26
|
def get_comm_token
|
29
|
-
@comm_token = nil
|
27
|
+
@comm_token = nil # request() uses it
|
30
28
|
@comm_token = request('getCommunicationToken', {:secretKey => Digest::MD5.hexdigest(@session)}, true)
|
31
29
|
@comm_token_ttl = Time.now.to_i
|
32
30
|
end
|
33
31
|
|
34
32
|
# Sign method
|
35
33
|
def create_token(method)
|
36
|
-
rnd =
|
37
|
-
salt =
|
34
|
+
rnd = get_random_hex_chars(6)
|
35
|
+
salt = get_method_salt(method)
|
38
36
|
plain = [method, @comm_token, salt, rnd].join(':')
|
39
37
|
hash = Digest::SHA1.hexdigest(plain)
|
40
38
|
"#{rnd}#{hash}"
|
41
39
|
end
|
40
|
+
|
41
|
+
def get_random_hex_chars(length)
|
42
|
+
chars = ('a'..'f').to_a | (0..9).to_a
|
43
|
+
(0...length).map { chars[rand(chars.length)] }.join
|
44
|
+
end
|
42
45
|
|
43
46
|
public
|
44
47
|
|
@@ -76,7 +79,7 @@ module Grooveshark
|
|
76
79
|
|
77
80
|
# Perform search request for query
|
78
81
|
def search(type, query)
|
79
|
-
results = request('
|
82
|
+
results = request('getResultsFromSearch', {:type => type, :query => query})['result']
|
80
83
|
results.map { |song| Song.new song }
|
81
84
|
end
|
82
85
|
|
@@ -92,19 +95,24 @@ module Grooveshark
|
|
92
95
|
|
93
96
|
# Get stream authentication by song ID
|
94
97
|
def get_stream_auth_by_songid(song_id)
|
95
|
-
request('getStreamKeyFromSongIDEx', {
|
96
|
-
'
|
97
|
-
'prefetch'
|
98
|
-
'
|
99
|
-
'country'
|
98
|
+
result = request('getStreamKeyFromSongIDEx', {
|
99
|
+
'type' => 0,
|
100
|
+
'prefetch' => false,
|
101
|
+
'songID' => song_id,
|
102
|
+
'country' => @country,
|
103
|
+
'mobile' => false,
|
100
104
|
})
|
105
|
+
if result == [] then
|
106
|
+
raise GeneralError, "No data for this song. Maybe Grooveshark banned your IP."
|
107
|
+
end
|
108
|
+
result
|
101
109
|
end
|
102
110
|
|
103
111
|
# Get stream authentication for song object
|
104
112
|
def get_stream_auth(song)
|
105
113
|
get_stream_auth_by_songid(song.id)
|
106
114
|
end
|
107
|
-
|
115
|
+
|
108
116
|
# Get song stream url by ID
|
109
117
|
def get_song_url_by_id(id)
|
110
118
|
resp = get_stream_auth_by_songid(id)
|
@@ -115,5 +123,11 @@ module Grooveshark
|
|
115
123
|
def get_song_url(song)
|
116
124
|
get_song_url_by_id(song.id)
|
117
125
|
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def get_method_salt(method)
|
130
|
+
get_method_client(method) == 'jsqueue' ? 'spiralLightbulbs' : 'riceAndChicken'
|
131
|
+
end
|
118
132
|
end
|
119
|
-
end
|
133
|
+
end
|
data/lib/grooveshark/request.rb
CHANGED
@@ -1,50 +1,39 @@
|
|
1
1
|
module Grooveshark
|
2
2
|
module Request
|
3
|
-
|
4
|
-
UUID = 'A3B724BA-14F5-4932-98B8-8D375F85F266'
|
5
|
-
CLIENT = 'htmlshark'
|
6
|
-
CLIENT_REV = '20110906'
|
7
|
-
COUNTRY = {"CC2" => "0", "IPR" => "353", "CC4" => "1073741824", "CC3" => "0", "CC1" => "0", "ID" => "223"}
|
8
|
-
TOKEN_TTL = 120 # 2 minutes
|
9
|
-
|
10
|
-
# Client overrides for different methods
|
11
|
-
METHOD_CLIENTS = {
|
12
|
-
'getStreamKeyFromSongIDEx' => 'jsqueue'
|
13
|
-
}
|
3
|
+
TOKEN_TTL = 120 # 2 minutes
|
14
4
|
|
15
5
|
# Perform API request
|
16
6
|
def request(method, params={}, secure=false)
|
17
7
|
refresh_token if @comm_token
|
18
8
|
|
19
|
-
|
20
|
-
url = "#{secure ? 'https' : 'http'}://#{API_BASE}/more.php?#{method}"
|
9
|
+
url = "#{secure ? 'https' : 'http'}://grooveshark.com/more.php?#{method}"
|
21
10
|
body = {
|
22
11
|
'header' => {
|
12
|
+
'client' => get_method_client(method),
|
13
|
+
'clientRevision' => get_method_client_revision(method),
|
14
|
+
'country' => @country,
|
15
|
+
'privacy' => 0,
|
23
16
|
'session' => @session,
|
24
|
-
'uuid' =>
|
25
|
-
'client' => agent,
|
26
|
-
'clientRevision' => CLIENT_REV,
|
27
|
-
'country' => COUNTRY
|
17
|
+
'uuid' => @uuid
|
28
18
|
},
|
29
19
|
'method' => method,
|
30
20
|
'parameters' => params
|
31
21
|
}
|
32
22
|
body['header']['token'] = create_token(method) if @comm_token
|
33
|
-
|
23
|
+
|
34
24
|
begin
|
35
|
-
data = RestClient.post(
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
)
|
25
|
+
data = RestClient.post(url, body.to_json, {
|
26
|
+
'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.77 Safari/537.1',
|
27
|
+
'Content-Type' => 'application/json',
|
28
|
+
'Referer' => get_method_referer(method)
|
29
|
+
})
|
41
30
|
rescue Exception => ex
|
42
|
-
raise GeneralError
|
31
|
+
raise GeneralError, ex.message
|
43
32
|
end
|
44
|
-
|
33
|
+
|
45
34
|
data = JSON.parse(data)
|
46
35
|
data = data.normalize if data.kind_of?(Hash)
|
47
|
-
|
36
|
+
|
48
37
|
if data.key?('fault')
|
49
38
|
raise ApiError.new(data['fault'])
|
50
39
|
else
|
@@ -56,5 +45,27 @@ module Grooveshark
|
|
56
45
|
def refresh_token
|
57
46
|
get_comm_token if Time.now.to_i - @comm_token_ttl > TOKEN_TTL
|
58
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def get_method_client(method)
|
52
|
+
jsqueue_methods = [
|
53
|
+
'getStreamKeyFromSongIDEx',
|
54
|
+
'addSongsToQueue',
|
55
|
+
'markSongDownloadedEx',
|
56
|
+
'markStreamKeyOver30Seconds',
|
57
|
+
'markSongQueueSongPlayed',
|
58
|
+
'markSongComplete'
|
59
|
+
]
|
60
|
+
jsqueue_methods.include?(method) ? 'jsqueue' : 'htmlshark'
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_method_client_revision(method)
|
64
|
+
get_method_client(method) == 'jsqueue' ? '20120227' : '20120227.01'
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_method_referer(method)
|
68
|
+
"http://grooveshark.com/JSQueue.swf?20120521.02" if get_method_client(method) == 'jsqueue'
|
69
|
+
end
|
59
70
|
end
|
60
|
-
end
|
71
|
+
end
|
data/lib/grooveshark/version.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -7,6 +7,18 @@ describe 'Client' do
|
|
7
7
|
@gs.session.should_not == nil
|
8
8
|
@gs.session.should match /^[abcdef\d]{32}$/i
|
9
9
|
end
|
10
|
+
|
11
|
+
it 'should have a valid country' do
|
12
|
+
gs = Grooveshark::Client.new
|
13
|
+
gs.country.should be_a_kind_of Hash
|
14
|
+
gs.country.size.should == 7
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should have a valid token' do
|
18
|
+
@gs = Grooveshark::Client.new
|
19
|
+
@gs.comm_token.should_not == nil
|
20
|
+
@gs.comm_token.should match /^[abcdef\d]{13}$/i
|
21
|
+
end
|
10
22
|
end
|
11
23
|
|
12
24
|
context 'authentication' do
|
@@ -33,4 +45,26 @@ describe 'Client' do
|
|
33
45
|
songs.size.should_not == 0
|
34
46
|
end
|
35
47
|
end
|
36
|
-
|
48
|
+
|
49
|
+
context 'download' do
|
50
|
+
it 'should download without being banned' do
|
51
|
+
4.times do # Usually the IP is banned on the 4th request on protocol mismatch
|
52
|
+
gs = Grooveshark::Client.new
|
53
|
+
# Try with a short song (this one is about a minute long)
|
54
|
+
song = gs.search_songs("Alan Reeves The Chase").first
|
55
|
+
url = gs.get_song_url(song)
|
56
|
+
file = RestClient::Request.execute(:method => :post, :url => url, :raw_response => true).file
|
57
|
+
case mime_type = `file -b --mime-type #{file.path}`.strip
|
58
|
+
when /^audio\//
|
59
|
+
# This is the expected type
|
60
|
+
when /^application\/octet-stream$/
|
61
|
+
# Sometimes the file type can't be detected and this type is returned. At least we
|
62
|
+
# check it's big enough to be an audio file.
|
63
|
+
file.size.should >= 500 * 1024
|
64
|
+
else
|
65
|
+
raise RSpec::Expectations::ExpectationNotMetError, "Unknown MIME type (#{mime_type})"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/spec/request_spec.rb
CHANGED
@@ -3,21 +3,23 @@ require File.expand_path("./helper", File.dirname(__FILE__))
|
|
3
3
|
describe 'Request' do
|
4
4
|
module Grooveshark
|
5
5
|
module Request
|
6
|
-
|
6
|
+
# Override default ttl for tests
|
7
|
+
remove_const :TOKEN_TTL # Avoid the "already initialized constant" warning
|
8
|
+
TOKEN_TTL = 1
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
10
|
-
it 'should obtain a new communication token on TTL expiration' do
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
12
|
+
# it 'should obtain a new communication token on TTL expiration' do
|
13
|
+
# @gs = Grooveshark::Client.new
|
14
|
+
# @tokens = []
|
15
|
+
#
|
16
|
+
# 3.times do |i|
|
17
|
+
# @gs.search_songs('Muse')
|
18
|
+
# @tokens << @gs.comm_token
|
19
|
+
# sleep 3
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# @tokens.uniq!
|
23
|
+
# @tokens.size.should == 3
|
24
|
+
# end
|
25
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grooveshark
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-08-19 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &2162457280 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '2.6'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2162457280
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: json
|
27
|
-
requirement: &
|
27
|
+
requirement: &2162455760 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 1.4.6
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2162455760
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rest-client
|
38
|
-
requirement: &
|
38
|
+
requirement: &2162448280 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: 1.5.1
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2162448280
|
47
47
|
description: Unofficial ruby library for consuming the Grooveshark API.
|
48
48
|
email: dan.sosedoff@gmail.com
|
49
49
|
executables: []
|
@@ -52,6 +52,7 @@ extra_rdoc_files: []
|
|
52
52
|
files:
|
53
53
|
- .gitignore
|
54
54
|
- .rspec
|
55
|
+
- Gemfile
|
55
56
|
- README.rdoc
|
56
57
|
- Rakefile
|
57
58
|
- grooveshark.gemspec
|
@@ -88,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
89
|
version: '0'
|
89
90
|
requirements: []
|
90
91
|
rubyforge_project:
|
91
|
-
rubygems_version: 1.8.
|
92
|
+
rubygems_version: 1.8.15
|
92
93
|
signing_key:
|
93
94
|
specification_version: 3
|
94
95
|
summary: Grooveshark API
|
@@ -97,3 +98,4 @@ test_files:
|
|
97
98
|
- spec/helper.rb
|
98
99
|
- spec/request_spec.rb
|
99
100
|
- spec/utils_spec.rb
|
101
|
+
has_rdoc:
|