spotifiery 0.0.1
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/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +50 -0
- data/Rakefile +2 -0
- data/lib/spotifiery/search_result/base.rb +44 -0
- data/lib/spotifiery/search_result.rb +12 -0
- data/lib/spotifiery/searchable/album.rb +40 -0
- data/lib/spotifiery/searchable/artist.rb +13 -0
- data/lib/spotifiery/searchable/base.rb +158 -0
- data/lib/spotifiery/searchable/track.rb +13 -0
- data/lib/spotifiery/searchable.rb +14 -0
- data/lib/spotifiery/version.rb +3 -0
- data/lib/spotifiery.rb +13 -0
- data/spec/fixtures/vcr_cassettes/album_find.yml +816 -0
- data/spec/fixtures/vcr_cassettes/album_lookup_by_uri.yml +1293 -0
- data/spec/fixtures/vcr_cassettes/artist_find.yml +45 -0
- data/spec/fixtures/vcr_cassettes/track_find.yml +1255 -0
- data/spec/fixtures/vcr_cassettes/track_lookup_by_uri.yml +2169 -0
- data/spec/fixtures/vcr_cassettes/with_or_without_you.yml +1240 -0
- data/spec/lib/spotifiery/search_result/base_spec.rb +55 -0
- data/spec/lib/spotifiery/search_result_spec.rb +2 -0
- data/spec/lib/spotifiery/searchable/album_spec.rb +86 -0
- data/spec/lib/spotifiery/searchable/artist_spec.rb +68 -0
- data/spec/lib/spotifiery/searchable/base_spec.rb +41 -0
- data/spec/lib/spotifiery/searchable/track_spec.rb +92 -0
- data/spec/lib/spotifiery/searchable_spec.rb +2 -0
- data/spec/lib/spotifiery_spec.rb +2 -0
- data/spec/spec_helper.rb +11 -0
- data/spotifiery.gemspec +26 -0
- metadata +170 -0
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
|
+
spotifiery.sublime-project
|
|
19
|
+
spotifiery.sublime-workspace
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2012 cicloon
|
|
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,50 @@
|
|
|
1
|
+
# Spotifiery
|
|
2
|
+
|
|
3
|
+
Spotifiery is a simple wrapper for the Spotify web API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'spotifiery'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install spotifiery
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Using it is very simple:
|
|
22
|
+
|
|
23
|
+
There are three "searchable items":
|
|
24
|
+
|
|
25
|
+
- Track
|
|
26
|
+
- Album
|
|
27
|
+
- Artist
|
|
28
|
+
|
|
29
|
+
To search for a track you can do:
|
|
30
|
+
|
|
31
|
+
Spotifiery::Searchable::Track.find(:q => 'Stairway to Heaven')
|
|
32
|
+
|
|
33
|
+
Which will return a SearchResult containing tracks called "Stairway to Heaven".
|
|
34
|
+
|
|
35
|
+
It accepts the same params as the spotify API so if you want to look for the page 2 of search results you should do:
|
|
36
|
+
|
|
37
|
+
Spotifiery::Searchable::Track.find(:q => 'Stairway to Heaven', :page => 2)
|
|
38
|
+
|
|
39
|
+
A find will return a SearchResult object which contains metadata about the query performed like "num_results", "limit", "offset", "page"... and of course the result set, which can be accessed via the results method (if you've searched for tracks you can use the tracks method too, the same applies to albums or artists).
|
|
40
|
+
|
|
41
|
+
A Searchable contains all the metadata about itself and allows navigation through the API, p.e.: you can ask a track about its artits or about its album.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## Contributing
|
|
45
|
+
|
|
46
|
+
1. Fork it
|
|
47
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
48
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
|
49
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
50
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
module Spotifiery
|
|
6
|
+
|
|
7
|
+
module SearchResult
|
|
8
|
+
|
|
9
|
+
class Base
|
|
10
|
+
|
|
11
|
+
def initialize response
|
|
12
|
+
|
|
13
|
+
response_hash = HashWithIndifferentAccess.new response
|
|
14
|
+
|
|
15
|
+
@info = response_hash[:info]
|
|
16
|
+
response_results = response_hash[ ActiveSupport::Inflector.pluralize(@info[:type]) ]
|
|
17
|
+
response_class = ActiveSupport::Inflector.constantize( "Spotifiery::Searchable::" + ActiveSupport::Inflector.titleize(@info[:type]))
|
|
18
|
+
@results = response_results.map{|result_hash| response_class.new(result_hash) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def results
|
|
22
|
+
@results
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def method_missing(method, *args, &block)
|
|
27
|
+
if defined?(@info) && @info.has_key?(method)
|
|
28
|
+
@info[method]
|
|
29
|
+
elsif defined?(@info) && method.eql?( ActiveSupport::Inflector.pluralize(@info[:type]).to_sym )
|
|
30
|
+
@results
|
|
31
|
+
else
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def respond_to_missing?(method, include_private = false)
|
|
37
|
+
@info.has_key?(method) || method.eql?(ActiveSupport::Inflector.pluralize(@info[:type]).to_sym)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Spotifiery
|
|
2
|
+
|
|
3
|
+
module Searchable
|
|
4
|
+
|
|
5
|
+
class Album
|
|
6
|
+
include Base
|
|
7
|
+
LOOKUPS = ['artist' , 'track']
|
|
8
|
+
QUERY_EXTRA_DEFAULT = 'trackdetail'
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Spotify messed it up with albums and artists.
|
|
13
|
+
# An Album lookup returns artist as artist-id: "spotify:artist:7jy3rLJdDQY21OgRLCZ9sD" and artist: "Foo Fighters"
|
|
14
|
+
# An Album search returns artists as [{href: "spotify:artist:7jy3rLJdDQY21OgRLCZ9sD",name: "Foo Fighters"}]
|
|
15
|
+
# This is fixed here
|
|
16
|
+
def lookup_in_spotify
|
|
17
|
+
perform_lookup
|
|
18
|
+
parsed_response = @lookup_response.parsed_response
|
|
19
|
+
searchable_type = self.class.name.demodulize.downcase
|
|
20
|
+
|
|
21
|
+
# Build the artist hash as it is on search results
|
|
22
|
+
artist_hash = HashWithIndifferentAccess.new({ :name => parsed_response[searchable_type]['artist'], :href => parsed_response[searchable_type]['artist-id']})
|
|
23
|
+
|
|
24
|
+
# Remove artist references from lookup response
|
|
25
|
+
parsed_response[searchable_type].delete(:artist)
|
|
26
|
+
parsed_response[searchable_type].delete('artist-id')
|
|
27
|
+
|
|
28
|
+
# Set artists as it is on search results
|
|
29
|
+
parsed_response[searchable_type]['artists'] = [artist_hash]
|
|
30
|
+
|
|
31
|
+
# Initialize attributes as always
|
|
32
|
+
set_base_attributes_by_lookup parsed_response
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
require 'active_support/core_ext/object'
|
|
2
|
+
require 'active_support/core_ext/string'
|
|
3
|
+
|
|
4
|
+
module Spotifiery
|
|
5
|
+
|
|
6
|
+
module Searchable
|
|
7
|
+
|
|
8
|
+
module Base
|
|
9
|
+
|
|
10
|
+
extend ActiveSupport::Concern
|
|
11
|
+
BASE_SEARCH_URL = "http://ws.spotify.com/search/1/"
|
|
12
|
+
BASE_LOOKUP_URL = "http://ws.spotify.com/lookup/1/.json"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
include HTTParty
|
|
17
|
+
base_uri BASE_SEARCH_URL
|
|
18
|
+
format :json
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module ClassMethods
|
|
22
|
+
|
|
23
|
+
# Search for something (track, album or artist).
|
|
24
|
+
# Accept the same params as spotify search API
|
|
25
|
+
def find opts = {}
|
|
26
|
+
parsed_response = ask_spotify opts
|
|
27
|
+
Spotifiery::SearchResult::Base.new parsed_response
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def ask_spotify opts = {}
|
|
33
|
+
opts = {:q => '', :page => '1'}.merge opts
|
|
34
|
+
self.get("/#{name.demodulize.downcase}.json" , :query => opts.to_param ).parsed_response
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Can be initialized from a spotify uri or from an search result hash.
|
|
40
|
+
# This way Spotify is not asked when initialized from a search result when name, popularity, etc are called
|
|
41
|
+
def initialize spotify_uri_or_hash
|
|
42
|
+
|
|
43
|
+
if spotify_uri_or_hash.is_a? Hash
|
|
44
|
+
initialize_base_attributes_getters HashWithIndifferentAccess.new spotify_uri_or_hash
|
|
45
|
+
@href = spotify_uri_or_hash['href']
|
|
46
|
+
|
|
47
|
+
elsif spotify_uri_or_hash.is_a? String
|
|
48
|
+
|
|
49
|
+
@href = spotify_uri_or_hash
|
|
50
|
+
|
|
51
|
+
else
|
|
52
|
+
raise "Wrong initialize params"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def method_missing(method, *args, &block)
|
|
60
|
+
|
|
61
|
+
# Looked in spotify before?
|
|
62
|
+
if !defined?(@lookup_response)
|
|
63
|
+
lookup_in_spotify
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Try to find the method in base_attributes
|
|
67
|
+
if @base_attributes.has_key? method
|
|
68
|
+
# Always try to parse for integers
|
|
69
|
+
Integer(@base_attributes[method],10) rescue @base_attributes[method]
|
|
70
|
+
# If not try to see if an instance variable is defined
|
|
71
|
+
elsif instance_variable_defined?("@#{method}")
|
|
72
|
+
instance_variable_get("@#{method}")
|
|
73
|
+
else
|
|
74
|
+
super
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def respond_to_missing?(method, include_private = false)
|
|
80
|
+
if !defined? @lookup_response
|
|
81
|
+
lookup_in_spotify
|
|
82
|
+
end
|
|
83
|
+
@base_attributes.has_key?(method) || instance_variable_defined?("@#{method}")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Perform a lookup, taking all the metadata from Spotify.
|
|
87
|
+
def lookup_in_spotify
|
|
88
|
+
perform_lookup
|
|
89
|
+
parsed_response = HashWithIndifferentAccess.new @lookup_response.parsed_response
|
|
90
|
+
set_base_attributes_by_lookup parsed_response
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def perform_lookup
|
|
94
|
+
@lookup_response = HTTParty.get( Spotifiery::Searchable::Base::BASE_LOOKUP_URL, :query => {:uri => @href, :extras => self.class::QUERY_EXTRA_DEFAULT } )
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Define base attribute methods from an Spotify lookup
|
|
98
|
+
def set_base_attributes_by_lookup attributes_hash
|
|
99
|
+
searchable_type = self.class.name.demodulize.downcase
|
|
100
|
+
initialize_base_attributes_getters attributes_hash[ searchable_type ]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Define methods from a hash
|
|
104
|
+
def initialize_base_attributes_getters attributes_hash
|
|
105
|
+
|
|
106
|
+
@base_attributes ||= {}
|
|
107
|
+
|
|
108
|
+
attributes_hash.each do |key, value|
|
|
109
|
+
|
|
110
|
+
attribute = key.underscore.to_sym
|
|
111
|
+
if !self.class::LOOKUPS.include? attribute.to_s.singularize
|
|
112
|
+
|
|
113
|
+
@base_attributes[ attribute ] = value
|
|
114
|
+
define_base_method attribute if !defined? attribute
|
|
115
|
+
|
|
116
|
+
else
|
|
117
|
+
initialize_lookup attribute, value
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Define methods that require new searchables
|
|
123
|
+
def initialize_lookup attribute, value
|
|
124
|
+
singular_attribute = attribute.to_s.singularize
|
|
125
|
+
searchable_class = ("Spotifiery::Searchable::" + singular_attribute.titleize ).constantize
|
|
126
|
+
|
|
127
|
+
searchable_instance = if value.is_a? Array
|
|
128
|
+
value.map {|val| searchable_class.new val }
|
|
129
|
+
else
|
|
130
|
+
searchable_class.new value
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
instance_variable_set("@#{attribute}", searchable_instance)
|
|
134
|
+
define_searchable_method attribute
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def define_base_method method
|
|
138
|
+
send(:define_singleton_method, method) do
|
|
139
|
+
# Always try to parse for integers
|
|
140
|
+
Integer(@base_attributes[attribute],10) rescue @base_attributes[attribute]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def define_searchable_method method
|
|
145
|
+
send(:define_singleton_method, method) do
|
|
146
|
+
instance_variable_get("@#{method}")
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
end
|