pandora_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []