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 ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ bin/
19
+ vendor/
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2012-05-20)
2
+
3
+ * First release. Basic Tuner API with station song fetching support.
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'pry'
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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'bundler/gem_tasks'
data/lib/pandora.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'pandora/version'
2
+
3
+ require 'pandora/partner'
4
+ require 'pandora/user'
@@ -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
@@ -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
@@ -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,3 @@
1
+ module Pandora
2
+ VERSION = '0.1.0'
3
+ 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: []