pandora_client 0.1.0
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 +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: []
|