pandora_client 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +5 -0
- data/LICENSE +22 -0
- data/README.md +38 -0
- data/Rakefile +3 -0
- data/lib/pandora.rb +4 -0
- data/lib/pandora/partner.rb +60 -0
- data/lib/pandora/song.rb +79 -0
- data/lib/pandora/station.rb +75 -0
- data/lib/pandora/user.rb +51 -0
- data/lib/pandora/util/client.rb +69 -0
- data/lib/pandora/util/cryptor.rb +50 -0
- data/lib/pandora/util/errors.rb +68 -0
- data/lib/pandora/version.rb +3 -0
- data/lib/pandora_client.rb +1 -0
- data/pandora_client.gemspec +25 -0
- metadata +110 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Gopal Patel
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
Pandora API Ruby Client
|
2
|
+
=======================
|
3
|
+
|
4
|
+
A Ruby wrapper for the [Pandora Tuner JSON API][tuner_api].
|
5
|
+
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'pandora'
|
11
|
+
|
12
|
+
partner = Pandora::Partner.new(username, password, device, encryption_key, decryption_key)
|
13
|
+
|
14
|
+
john = partner.login_user(john_email, john_password) # Returns a Pandora::User
|
15
|
+
john.stations
|
16
|
+
|
17
|
+
jane = partner.login_user(jane_email, jane_pasword)
|
18
|
+
jane.stations
|
19
|
+
```
|
20
|
+
|
21
|
+
`Pandora::Partner` and `Pandora::User` objects can be marshalled via Ruby's
|
22
|
+
`Marshal`. In a web application this can be useful to avoid re-authenticating on
|
23
|
+
every request---just marshal the appropriate object to the user session or
|
24
|
+
temporary storage. Note that the marshalled data may contain sensitive passwords
|
25
|
+
or tokens.
|
26
|
+
|
27
|
+
|
28
|
+
## Contributing
|
29
|
+
|
30
|
+
Patches and bug reports are welcome. Just send a [pull request][pullrequests] or
|
31
|
+
file an [issue][issues]. [Project changelog][changelog].
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
[tuner_api]: http://pan-do-ra-api.wikia.com/wiki/Json/5
|
36
|
+
[pullrequests]: https://github.com/nixme/pandora_client/pulls
|
37
|
+
[issues]: https://github.com/nixme/pandora_client/issues
|
38
|
+
[changelog]: https://github.com/nixme/pandora_client/blob/master/CHANGELOG.md
|
data/Rakefile
ADDED
data/lib/pandora.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'pandora/util/client'
|
2
|
+
require 'pandora/util/cryptor'
|
3
|
+
|
4
|
+
module Pandora
|
5
|
+
class Partner
|
6
|
+
include Client
|
7
|
+
|
8
|
+
attr_reader :username, :password, :device, :encryption_key, :decryption_key
|
9
|
+
attr_reader :partner_id, :partner_auth_token, :time_offset
|
10
|
+
|
11
|
+
def initialize(username, password, device, encryption_key, decryption_key)
|
12
|
+
@username, @password, @device, @encryption_key, @decryption_key =
|
13
|
+
username, password, device, encryption_key, decryption_key
|
14
|
+
authenticate
|
15
|
+
end
|
16
|
+
|
17
|
+
def login_user(username, password)
|
18
|
+
User.new(self, username, password)
|
19
|
+
end
|
20
|
+
|
21
|
+
def cryptor
|
22
|
+
@cryptor ||= Cryptor.new(@encryption_key, @decryption_key)
|
23
|
+
end
|
24
|
+
|
25
|
+
def marshal_dump
|
26
|
+
[@username, @password, @device, @encryption_key, @decryption_key,
|
27
|
+
@partner_id, @partner_auth_token, @time_offset]
|
28
|
+
end
|
29
|
+
|
30
|
+
def marshal_load(objects)
|
31
|
+
@username, @password, @device, @encryption_key, @decryption_key,
|
32
|
+
@partner_id, @partner_auth_token, @time_offset = objects
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def authenticate
|
39
|
+
result = call 'auth.partnerLogin', { secure: true, encrypt: false }, {
|
40
|
+
username: @username,
|
41
|
+
password: @password,
|
42
|
+
deviceModel: @device,
|
43
|
+
version: '5'
|
44
|
+
}
|
45
|
+
|
46
|
+
@partner_id = result['partnerId']
|
47
|
+
@partner_auth_token = result['partnerAuthToken']
|
48
|
+
server_time = cryptor.decrypt(result['syncTime'])[4..-1].to_i
|
49
|
+
@time_offset = Time.now.to_i - server_time
|
50
|
+
end
|
51
|
+
|
52
|
+
def user_auth_token
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def user_id
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/pandora/song.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'pandora/util/client'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
module Pandora
|
6
|
+
class Song
|
7
|
+
include Client
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
ALL_AUDIO_FORMATS =
|
11
|
+
['HTTP_40_AAC_MONO', 'HTTP_64_AAC', 'HTTP_64_AACPLUS',
|
12
|
+
'HTTP_24_AACPLUS_ADTS', 'HTTP_32_AACPLUS_ADTS', 'HTTP_64_AACPLUS_ADTS',
|
13
|
+
'HTTP_128_MP3', 'HTTP_192_MP3', 'HTTP_32_WMA']
|
14
|
+
DEFAULT_AUDIO_FORMATS =
|
15
|
+
['HTTP_32_AACPLUS_ADTS', 'HTTP_64_AACPLUS_ADTS', 'HTTP_192_MP3']
|
16
|
+
|
17
|
+
attr_reader :title, :token, :artist, :album, :album_art_url, :rating, :gain,
|
18
|
+
:allow_feedback, :url, :album_url, :artist_url,
|
19
|
+
:amazon_album_asin, :amazon_album_digital_asin,
|
20
|
+
:amazon_song_digital_asin, :amazon_album_url, :itunes_song_url,
|
21
|
+
:song_explorer_url, :album_explorer_url, :artist_explorer_url,
|
22
|
+
:audio_url, :audio_urls
|
23
|
+
|
24
|
+
def_delegators :@station, *Client::ALL_STATE_ATTRIBUTES
|
25
|
+
|
26
|
+
def initialize(station, data, audio_formats)
|
27
|
+
@station = station
|
28
|
+
load_from_data(data, audio_formats)
|
29
|
+
end
|
30
|
+
|
31
|
+
def id
|
32
|
+
load_explorer_data unless @id
|
33
|
+
@id
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def load_from_data(data, audio_formats)
|
40
|
+
@title = data['songName']
|
41
|
+
@token = data['trackToken']
|
42
|
+
@artist = data['artistName']
|
43
|
+
@album = data['albumName']
|
44
|
+
@album_art_url = data['albumArtUrl']
|
45
|
+
@rating = data['songRating']
|
46
|
+
@gain = data['trackGain']
|
47
|
+
@allow_feedback = data['allowFeedback']
|
48
|
+
@url = data['songDetailUrl']
|
49
|
+
@album_url = data['albumDetailUrl']
|
50
|
+
@artist_url = data['artistDetailUrl']
|
51
|
+
|
52
|
+
@amazon_album_asin = data['amazonAlbumAsin']
|
53
|
+
@amazon_album_digital_asin = data['amazonAlbumDigitalAsin']
|
54
|
+
@amazon_song_digital_asin = data['amazonSongDigitalAsin']
|
55
|
+
@amazon_album_url = data['amazonAlbumUrl']
|
56
|
+
@itunes_song_url = data['itunesSongUrl']
|
57
|
+
|
58
|
+
@song_explorer_url = data['songExplorerUrl']
|
59
|
+
@album_explorer_url = data['albumExplorerUrl']
|
60
|
+
@artist_explorer_url = data['artistDetailUrl']
|
61
|
+
|
62
|
+
@audio_url = data['audioUrl']
|
63
|
+
@audio_urls =
|
64
|
+
if (additional_audio_urls = data['additionalAudioUrl'])
|
65
|
+
Hash[audio_formats.zip(additional_audio_urls)]
|
66
|
+
else
|
67
|
+
{}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Unfortunately the Tuner API track JSON doesn't include the musicId, a
|
72
|
+
# track identifier that's constant among different user sessions. However,
|
73
|
+
# we can fetch it via the Song's Explorer XML URL.
|
74
|
+
def load_explorer_data
|
75
|
+
document = Nokogiri::XML(Faraday.get(@song_explorer_url).body)
|
76
|
+
@id = document.search('songExplorer').first['musicId']
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'pandora/util/client'
|
2
|
+
require 'pandora/song'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Pandora
|
6
|
+
class Station
|
7
|
+
include Client
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
attr_reader :id, :name, :created_at, :url, :shared, :sharing_url,
|
11
|
+
:quick_mix, :quick_mix_station_ids, :allow_rename,
|
12
|
+
:allow_add_music, :allow_delete
|
13
|
+
attr_reader :user
|
14
|
+
attr_accessor :token
|
15
|
+
|
16
|
+
[:quick_mix, :allow_rename, :allow_add_music, :allow_delete].each do |method|
|
17
|
+
alias_method "#{method}?".to_sym, method
|
18
|
+
end
|
19
|
+
|
20
|
+
def_delegators :@user, *Client::ALL_STATE_ATTRIBUTES
|
21
|
+
|
22
|
+
def initialize(user, data = nil)
|
23
|
+
@user = user
|
24
|
+
load_from_data(data) if data
|
25
|
+
end
|
26
|
+
|
27
|
+
def rename(new_name)
|
28
|
+
# TODO: check allow_rename? force option?
|
29
|
+
result = call 'station.renameStation', { secure: false, encrypt: true }, {
|
30
|
+
stationToken: @token,
|
31
|
+
stationName: new_name
|
32
|
+
}
|
33
|
+
load_from_data(result)
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete
|
37
|
+
# TODO: check allow_delete? force option?
|
38
|
+
call 'station.deleteStation', { secure: false, encrypt: true }, {
|
39
|
+
stationToken: @token
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def next_songs(audio_formats = Song::DEFAULT_AUDIO_FORMATS)
|
44
|
+
result = call 'station.getPlaylist', { secure: true, encrypt: true }, {
|
45
|
+
stationToken: @token,
|
46
|
+
additionalAudioUrl: audio_formats.join(',')
|
47
|
+
}
|
48
|
+
|
49
|
+
result['items'].map do |song_data|
|
50
|
+
Song.new(self, song_data, audio_formats)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def load_from_data(data)
|
58
|
+
@id = data['stationId']
|
59
|
+
@name = data['stationName']
|
60
|
+
@token = data['stationToken']
|
61
|
+
@created_at = Time.at(data['dateCreated']['time'] / 1000.0)
|
62
|
+
@url = data['stationDetailUrl']
|
63
|
+
@shared = data['isShared']
|
64
|
+
@sharing_url = data['stationSharingUrl']
|
65
|
+
@quick_mix = data['isQuickMix']
|
66
|
+
@quick_mix_station_ids = data['quickMixStationIds']
|
67
|
+
@allow_rename = data['allowRename']
|
68
|
+
@allow_add_music = data['allowAddMusic']
|
69
|
+
@allow_delete = data['allowDelete']
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
# TODO: override inspect so cryptor doesn't ruin it
|
data/lib/pandora/user.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'pandora/util/client'
|
2
|
+
require 'pandora/station'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Pandora
|
6
|
+
class User
|
7
|
+
include Client
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
attr_reader :partner, :username, :password
|
11
|
+
attr_reader :user_auth_token, :user_id
|
12
|
+
|
13
|
+
def_delegators :@partner, :cryptor, :partner_id, :partner_auth_token,
|
14
|
+
:time_offset
|
15
|
+
|
16
|
+
def initialize(partner, username, password)
|
17
|
+
@partner, @username, @password = partner, username, password
|
18
|
+
login
|
19
|
+
end
|
20
|
+
|
21
|
+
def stations
|
22
|
+
call('user.getStationList')['stations'].map do |station_data|
|
23
|
+
Station.new(self, station_data)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def marshal_dump
|
28
|
+
[@partner, @username, @password, @user_auth_token, @user_id]
|
29
|
+
end
|
30
|
+
|
31
|
+
def marshal_load(objects)
|
32
|
+
@partner, @username, @password, @user_auth_token, @user_id = objects
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def login
|
39
|
+
result = call 'auth.userLogin', { secure: true, encrypt: true }, {
|
40
|
+
loginType: 'user',
|
41
|
+
username: username,
|
42
|
+
password: password
|
43
|
+
}
|
44
|
+
|
45
|
+
@user_auth_token = result['userAuthToken']
|
46
|
+
@user_id = result['userId']
|
47
|
+
|
48
|
+
result
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'pandora/util/errors'
|
2
|
+
require 'faraday'
|
3
|
+
require 'json' unless defined?(JSON)
|
4
|
+
|
5
|
+
module Pandora
|
6
|
+
|
7
|
+
# Standardized Tuner API calls
|
8
|
+
#
|
9
|
+
# Expects the target class to respond to the following methods:
|
10
|
+
# cryptor
|
11
|
+
# user_auth_token
|
12
|
+
# partner_auth_token
|
13
|
+
# partner_id
|
14
|
+
# user_id
|
15
|
+
# time_offset
|
16
|
+
#
|
17
|
+
module Client
|
18
|
+
private
|
19
|
+
|
20
|
+
ALL_STATE_ATTRIBUTES = [:cryptor, :user_auth_token, :partner_auth_token,
|
21
|
+
:partner_id, :user_id, :time_offset]
|
22
|
+
|
23
|
+
def call(method, options = { secure: false, encrypt: true }, payload = {})
|
24
|
+
connection = (options[:secure] ? secure_connection : insecure_connection)
|
25
|
+
response = connection.post do |request|
|
26
|
+
request.url 'services/json/'
|
27
|
+
request.headers['Content-Type'] = 'text/plain'
|
28
|
+
request.params['method'] = method
|
29
|
+
|
30
|
+
# Attach additional tokens and IDs as URL parameters if available
|
31
|
+
request.params['auth_token'] = user_auth_token || partner_auth_token
|
32
|
+
request.params['partner_id'] = partner_id if partner_id
|
33
|
+
request.params['user_id'] = user_id if user_id
|
34
|
+
|
35
|
+
# Attach tokens and server time to JSON payload
|
36
|
+
payload.merge!(syncTime: Time.now.to_i - time_offset) if time_offset
|
37
|
+
if user_auth_token
|
38
|
+
payload.merge!(userAuthToken: user_auth_token)
|
39
|
+
elsif partner_auth_token
|
40
|
+
payload.merge!(partnerAuthToken: partner_auth_token)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Optionally encrypt the JSON request payload
|
44
|
+
json = JSON.dump(payload)
|
45
|
+
request.body = (options[:encrypt] ? cryptor.encrypt(json) : json)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Check for API errors
|
49
|
+
json = JSON.load(response.body)
|
50
|
+
if json['stat'] != 'ok' || !json['result']
|
51
|
+
raise APIError.new(json['message'], json['code'])
|
52
|
+
end
|
53
|
+
|
54
|
+
json['result']
|
55
|
+
end
|
56
|
+
|
57
|
+
def secure_connection
|
58
|
+
@secure_connection ||= Faraday.new(url: "https://#{host}")
|
59
|
+
end
|
60
|
+
|
61
|
+
def insecure_connection
|
62
|
+
@insecure_connection ||= Faraday.new(url: "http://#{host}")
|
63
|
+
end
|
64
|
+
|
65
|
+
def host
|
66
|
+
'tuner.pandora.com'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'crypt/blowfish'
|
2
|
+
|
3
|
+
module Pandora
|
4
|
+
|
5
|
+
# Blowfish encryptor/decryptor for the Pandora Tuner API
|
6
|
+
#
|
7
|
+
# Ciphertext is round-tripped to and from ASCII-encoded hexadecimal
|
8
|
+
# characters.
|
9
|
+
#
|
10
|
+
# Keys must be provided. Available at
|
11
|
+
# http://pan-do-ra-api.wikia.com/wiki/Json/5/partners
|
12
|
+
#
|
13
|
+
class Cryptor
|
14
|
+
def initialize(encryption_key, decryption_key)
|
15
|
+
@encryptor = Crypt::Blowfish.new(encryption_key)
|
16
|
+
@decryptor = Crypt::Blowfish.new(decryption_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def encrypt(str)
|
20
|
+
str.force_encoding(Encoding::BINARY).
|
21
|
+
scan(/.{1,8}/).map do |block| # Operate on 8 char chunks
|
22
|
+
block = pad(block, 8) if block.length < 8 # Pad to 8 chars if under
|
23
|
+
@encryptor.encrypt_block(block). # Encrypt the data
|
24
|
+
unpack('H*').first # Convert to ASCII hex
|
25
|
+
end.join('')
|
26
|
+
end
|
27
|
+
|
28
|
+
def decrypt(str)
|
29
|
+
str.force_encoding(Encoding::BINARY).
|
30
|
+
scan(/.{1,16}/).map do |block| # Operate on 16 char chunks
|
31
|
+
block = pad([block].pack('H*'), 8) # Convert ASCII hex to raw data
|
32
|
+
@decryptor.decrypt_block(block) # Decrypt the data
|
33
|
+
end.join('').
|
34
|
+
sub(/^(.*?)[[:cntrl:]]*$/, '\1') # Strip trailing junk
|
35
|
+
end
|
36
|
+
|
37
|
+
# Override inspect so it doesn't include internal Blowfish P-array and
|
38
|
+
# S-boxes from the instance variables.
|
39
|
+
def inspect
|
40
|
+
to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def pad(str, length)
|
47
|
+
str + ("\0" * [length - str.length, 0].max)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Pandora
|
2
|
+
# Raised on errors from the Tuner API
|
3
|
+
#
|
4
|
+
class APIError < StandardError
|
5
|
+
# API error code to reason mapping.
|
6
|
+
# From http://pan-do-ra-api.wikia.com/wiki/Json/5#Error_codes
|
7
|
+
ERROR_CODE_TO_REASON = {
|
8
|
+
0 => 'INTERNAL',
|
9
|
+
1 => 'MAINTENANCE_MODE',
|
10
|
+
2 => 'URL_PARAM_MISSING_METHOD',
|
11
|
+
3 => 'URL_PARAM_MISSING_AUTH_TOKEN',
|
12
|
+
4 => 'URL_PARAM_MISSING_PARTNER_ID',
|
13
|
+
5 => 'URL_PARAM_MISSING_USER_ID',
|
14
|
+
6 => 'SECURE_PROTOCOL_REQUIRED',
|
15
|
+
7 => 'CERTIFICATE_REQUIRED',
|
16
|
+
8 => 'PARAMETER_TYPE_MISMATCH',
|
17
|
+
9 => 'PARAMETER_MISSING',
|
18
|
+
10 => 'PARAMETER_VALUE_INVALID',
|
19
|
+
11 => 'API_VERSION_NOT_SUPPORTED',
|
20
|
+
12 => 'Pandora not available in this country',
|
21
|
+
13 => 'INSUFFICIENT_CONNECTIVITY. Bad sync time?',
|
22
|
+
14 => 'Unknown method name?',
|
23
|
+
15 => 'Wrong protocol (http/https)?',
|
24
|
+
1000 => 'READ_ONLY_MODE',
|
25
|
+
1001 => 'INVALID_AUTH_TOKEN',
|
26
|
+
1002 => 'INVALID_PARTNER_LOGIN',
|
27
|
+
1003 => 'LISTENER_NOT_AUTHORIZED. Account suspended, or Pandora One Subscription or Trial expired.',
|
28
|
+
1004 => 'USER_NOT_AUTHORIZED',
|
29
|
+
1005 => 'Station limit reached',
|
30
|
+
1006 => 'STATION_DOES_NOT_EXIST',
|
31
|
+
1007 => 'COMPLIMENTARY_PERIOD_ALREADY_IN_USE',
|
32
|
+
1008 => 'CALL_NOT_ALLOWED. Trying to add feedback to a shared station?',
|
33
|
+
1009 => 'DEVICE_NOT_FOUND',
|
34
|
+
1010 => 'PARTNER_NOT_AUTHORIZED',
|
35
|
+
1011 => 'INVALID_USERNAME',
|
36
|
+
1012 => 'INVALID_PASSWORD',
|
37
|
+
1013 => 'USERNAME_ALREADY_EXISTS',
|
38
|
+
1014 => 'DEVICE_ALREADY_ASSOCIATED_TO_ACCOUNT',
|
39
|
+
1015 => 'UPGRADE_DEVICE_MODEL_INVALID',
|
40
|
+
1018 => 'EXPLICIT_PIN_INCORRECT',
|
41
|
+
1020 => 'EXPLICIT_PIN_MALFORMED',
|
42
|
+
1023 => 'DEVICE_MODEL_INVALID',
|
43
|
+
1024 => 'ZIP_CODE_INVALID',
|
44
|
+
1025 => 'BIRTH_YEAR_INVALID',
|
45
|
+
1026 => 'BIRTH_YEAR_TOO_YOUNG',
|
46
|
+
1027 => 'INVALID_COUNTRY_CODE',
|
47
|
+
1027 => 'INVALID_GENDER',
|
48
|
+
1034 => 'DEVICE_DISABLED',
|
49
|
+
1035 => 'DAILY_TRIAL_LIMIT_REACHED',
|
50
|
+
1036 => 'INVALID_SPONSOR',
|
51
|
+
1037 => 'USER_ALREADY_USED_TRIAL'
|
52
|
+
}
|
53
|
+
|
54
|
+
attr_reader :api_message, :code
|
55
|
+
|
56
|
+
def initialize(api_message, code)
|
57
|
+
@api_message = api_message
|
58
|
+
@code = code.to_i
|
59
|
+
end
|
60
|
+
|
61
|
+
def message
|
62
|
+
msg = "#{@api_message} (Code: #{@code}"
|
63
|
+
reason = ERROR_CODE_TO_REASON[@code]
|
64
|
+
msg += " - #{reason}" if reason
|
65
|
+
msg + ')'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'pandora'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path('../lib/pandora/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'pandora_client'
|
7
|
+
gem.version = Pandora::VERSION
|
8
|
+
gem.author = 'Gopal Patel'
|
9
|
+
gem.email = 'nixme@stillhope.com'
|
10
|
+
gem.license = 'MIT'
|
11
|
+
gem.homepage = 'https://github.com/nixme/pandora_client'
|
12
|
+
gem.description = 'A ruby wrapper for the Pandora Tuner JSON API'
|
13
|
+
gem.summary = 'A ruby wrapper for the Pandora Tuner JSON API'
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($\)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
|
20
|
+
# Dependencies
|
21
|
+
gem.required_ruby_version = '>= 1.9.2'
|
22
|
+
gem.add_runtime_dependency 'faraday', '~> 0.8'
|
23
|
+
gem.add_runtime_dependency 'crypt19', '~> 1.2.1'
|
24
|
+
gem.add_runtime_dependency 'nokogiri', '~> 1.5.2'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pandora_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Gopal Patel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: faraday
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.8'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.8'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: crypt19
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.2.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.2.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: nokogiri
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.5.2
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.5.2
|
62
|
+
description: A ruby wrapper for the Pandora Tuner JSON API
|
63
|
+
email: nixme@stillhope.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- .gitignore
|
69
|
+
- CHANGELOG.md
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/pandora.rb
|
75
|
+
- lib/pandora/partner.rb
|
76
|
+
- lib/pandora/song.rb
|
77
|
+
- lib/pandora/station.rb
|
78
|
+
- lib/pandora/user.rb
|
79
|
+
- lib/pandora/util/client.rb
|
80
|
+
- lib/pandora/util/cryptor.rb
|
81
|
+
- lib/pandora/util/errors.rb
|
82
|
+
- lib/pandora/version.rb
|
83
|
+
- lib/pandora_client.rb
|
84
|
+
- pandora_client.gemspec
|
85
|
+
homepage: https://github.com/nixme/pandora_client
|
86
|
+
licenses:
|
87
|
+
- MIT
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 1.9.2
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.8.23
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: A ruby wrapper for the Pandora Tuner JSON API
|
110
|
+
test_files: []
|