spot 0.1.4 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +41 -75
- data/lib/spot.rb +173 -178
- data/lib/spot/album.rb +4 -4
- data/lib/spot/artist.rb +2 -2
- data/lib/spot/base.rb +1 -1
- data/lib/spot/clean.rb +55 -0
- data/lib/spot/exclude.yml +3 -15
- data/lib/spot/ignore.yml +14 -0
- data/lib/spot/song.rb +6 -6
- data/spec/album_spec.rb +4 -4
- data/spec/artist_spec.rb +3 -3
- data/spec/base_spec.rb +2 -2
- data/spec/clean_spec.rb +113 -0
- data/spec/fixtures/vcr_cassettes/spotify.yml +5256 -0
- data/spec/song_spec.rb +7 -17
- data/spec/spec_helper.rb +13 -9
- data/spec/spot_spec.rb +221 -0
- data/spot.gemspec +4 -3
- metadata +95 -86
- data/spec/spotify_spec.rb +0 -435
data/README.markdown
CHANGED
@@ -10,95 +10,69 @@ Follow me on [Twitter](http://twitter.com/linusoleander) for more info and updat
|
|
10
10
|
|
11
11
|
### Find a song
|
12
12
|
|
13
|
-
The `Spot.find_song` method returns the first hit.
|
13
|
+
The `Spot::Search.find_song` method returns the first hit.
|
14
14
|
|
15
15
|
```` ruby
|
16
|
-
Spot.find_song("Like Glue")
|
16
|
+
Spot::Search.find_song("Like Glue")
|
17
17
|
````
|
18
18
|
|
19
19
|
### Find all songs
|
20
20
|
|
21
|
-
The `find_all_songs` method returns a list of `Song` objects.
|
21
|
+
The `find_all_songs` method returns a list of `Spot::Song` objects.
|
22
22
|
|
23
23
|
```` ruby
|
24
|
-
Spot.find_all_songs("Like Glue")
|
24
|
+
Spot::Search.find_all_songs("Like Glue")
|
25
25
|
````
|
26
26
|
|
27
27
|
### Find an artist
|
28
28
|
|
29
|
-
The `Spot.find_artist` method returns the first hit.
|
29
|
+
The `Spot::Search.find_artist` method returns the first hit.
|
30
30
|
|
31
31
|
```` ruby
|
32
|
-
Spot.find_artist("Madonna")
|
32
|
+
Spot::Search.find_artist("Madonna")
|
33
33
|
````
|
34
34
|
|
35
35
|
### Find all artists
|
36
36
|
|
37
|
-
The `find_all_artists` method returns a list of `Artist` objects.
|
37
|
+
The `find_all_artists` method returns a list of `Spot::Artist` objects.
|
38
38
|
|
39
39
|
```` ruby
|
40
|
-
Spot.find_all_artists("Madonna")
|
40
|
+
Spot::Search.find_all_artists("Madonna")
|
41
41
|
````
|
42
42
|
|
43
43
|
### Find an album
|
44
44
|
|
45
|
-
The `Spot.find_album` method returns the first hit.
|
45
|
+
The `Spot::Search.find_album` method returns the first hit.
|
46
46
|
|
47
47
|
```` ruby
|
48
|
-
Spot.find_album("Old Skool Of Rock")
|
48
|
+
Spot::Search.find_album("Old Skool Of Rock")
|
49
49
|
````
|
50
50
|
|
51
51
|
### Find all albums
|
52
52
|
|
53
|
-
The `find_all_albums` method returns a list of `Album` objects.
|
53
|
+
The `find_all_albums` method returns a list of `Spot::Album` objects.
|
54
54
|
|
55
55
|
```` ruby
|
56
|
-
Spot.find_all_albums("Old Skool Of Rock")
|
56
|
+
Spot::Search.find_all_albums("Old Skool Of Rock")
|
57
57
|
````
|
58
58
|
|
59
59
|
### Find best match
|
60
60
|
|
61
|
-
The `prime` method makes it possible to fetch the best matching result based on the ingoing argument.
|
61
|
+
The `prime` method makes it possible to fetch the best matching result based on the ingoing argument. It will reject data (songs, artists and albums) that contains any of the [these words](https://github.com/oleander/Spot/blob/master/lib/spot/ignore.yml).
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
=> Home Sweet Home - Mötley Crüe
|
67
|
-
|
68
|
-
Here is what is being returned *with* the `prime` method.
|
69
|
-
|
70
|
-
>> Spot.prime.find_song("sweet home").result
|
71
|
-
=> Sweet Home Alabama - Lynyrd Skynyrd
|
72
|
-
|
73
|
-
The `prime` method will reject data (songs, artists and albums) that contains any of the [these words](https://github.com/oleander/Spot/blob/master/lib/spot/exclude.yml).
|
74
|
-
|
75
|
-
Here is the short version.
|
76
|
-
|
77
|
-
- tribute
|
78
|
-
- cover
|
79
|
-
- remix
|
80
|
-
- live
|
81
|
-
- club mix
|
82
|
-
- karaoke
|
83
|
-
- remaster
|
84
|
-
- club version
|
85
|
-
- demo
|
86
|
-
- made famous by
|
87
|
-
- remixes
|
88
|
-
- instrumental
|
89
|
-
- ringtone
|
90
|
-
|
91
|
-
Take a look at the [source code](https://github.com/oleander/Spot/blob/master/lib/spot.rb#L94) for more information.
|
63
|
+
``` ruby
|
64
|
+
Spot::Search.prime.find_song("Sweet Home Alabama")
|
65
|
+
```
|
92
66
|
|
93
67
|
### Specify a territory
|
94
68
|
|
95
|
-
All songs in Spotify isn't available everywhere.
|
96
|
-
|
69
|
+
All songs in Spotify isn't available everywhere.
|
70
|
+
It might therefore be usefull to specify a location, also know as a *territory*.
|
97
71
|
|
98
72
|
If you for example want to find all songs available in Sweden, then you might do something like this.
|
99
73
|
|
100
74
|
```` ruby
|
101
|
-
Spot.territory("SE").find_song("Sweet Home Alabama")
|
75
|
+
Spot::Search.territory("SE").find_song("Sweet Home Alabama")
|
102
76
|
````
|
103
77
|
|
104
78
|
You can find the complete territory list [here](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).
|
@@ -109,97 +83,89 @@ Sometimes it may be useful to filer ingoing params.
|
|
109
83
|
You can filter the ingoing string by using the `strip` method.
|
110
84
|
|
111
85
|
```` ruby
|
112
|
-
Spot.strip.find_song("3. Who's That Chick ? feat.Rihanna [Singel Version] - (Single)")
|
86
|
+
Spot::Search.strip.find_song("3. Who's That Chick ? feat.Rihanna [Singel Version] - (Single)")
|
113
87
|
````
|
114
88
|
|
115
89
|
This is the string that is being passed to Spot.
|
116
90
|
|
117
91
|
"who's that chick ?"
|
118
92
|
|
119
|
-
Take a look at the [source code](https://github.com/oleander/Spot/blob/master/lib/spot.rb#L136) if you want to know what regexp is being used.
|
120
|
-
|
121
93
|
### Specify a page
|
122
94
|
|
123
|
-
You can easily select any page you want by defining the `page` method.
|
124
|
-
|
125
95
|
```` ruby
|
126
|
-
Spot.page(11).find_song("sweet home")
|
96
|
+
Spot::Search.page(11).find_song("sweet home")
|
127
97
|
````
|
128
98
|
|
129
|
-
The default page is of course `1`. :)
|
130
|
-
|
131
99
|
### Combine methods
|
132
100
|
|
133
|
-
You can easily
|
101
|
+
You can easily chain method like this.
|
134
102
|
|
135
103
|
```` ruby
|
136
|
-
Spot.page(11).territory("SE").prime.strip.find_song("sweet home")
|
104
|
+
Spot::Search.page(11).territory("SE").prime.strip.find_song("sweet home")
|
137
105
|
````
|
138
106
|
|
139
107
|
## Data to work with
|
140
108
|
|
141
109
|
As soon as the `result` or `results` method is applied to the query a request to Spotify is made.
|
142
110
|
|
143
|
-
Here is an example
|
111
|
+
Here is an example (`#result`).
|
144
112
|
|
145
|
-
>> song = Spot.find_song("sweet home").result
|
113
|
+
>> song = Spot::Search.find_song("sweet home").result
|
146
114
|
|
147
115
|
>> puts song.title
|
148
116
|
=> Home Sweet Home
|
149
117
|
|
150
118
|
>> puts song.class
|
151
|
-
=>
|
119
|
+
=> Spot::Song
|
152
120
|
|
153
|
-
Here is an example
|
121
|
+
Here is an example (`#results`).
|
154
122
|
|
155
|
-
>> songs = Spot.find_all_songs("sweet home").results
|
123
|
+
>> songs = Spot::Search.find_all_songs("sweet home").results
|
156
124
|
>> puts songs.count
|
157
125
|
=> 100
|
158
126
|
|
159
127
|
### Base
|
160
128
|
|
161
|
-
|
129
|
+
`Spot::Song`, `Spot::Artist` and `Spot::Album` shares the following methods.
|
162
130
|
|
163
131
|
- **popularity** (*Float*) Popularity acording to Spotify. From `0.0` to `1.0`.
|
164
132
|
- **href** (*String*) Url for the specific object.
|
165
133
|
Default is a spotify url on this format: `spotify:track:5DhDGwNXRPHsMApbtVKvFb`.
|
166
|
-
`http` may be passed as a string, which will return an Spotify HTTP Url
|
134
|
+
`http` may be passed as a string, which will return an Spotify HTTP Url.
|
167
135
|
- **available?** (*Boolean*) Takes one argument, a territory. Returns true if the object is accessible in the given region.
|
168
136
|
Read more about it in the *Specify a territory* section above.
|
169
137
|
- **to_s** (*String*) A string representation of the object.
|
170
138
|
- **valid?** (*Boolean*) Returns true if the object is valid, a.k.a is accessible in the given territory.
|
171
139
|
If no territory is given, this will be true.
|
172
|
-
- **name** (*String*)
|
140
|
+
- **name** (*String*) Same as `Spot::Song#title`.
|
173
141
|
|
174
142
|
### Song
|
175
143
|
|
176
|
-
Methods available for the `Song` class.
|
144
|
+
Methods available for the `Spot::Song` class.
|
177
145
|
|
178
146
|
- **length** (*Fixnum*) Length in seconds.
|
179
147
|
- **title** (*String*) Song title.
|
180
|
-
- **to_s** (*String*) String representation of the object in this format: *
|
148
|
+
- **to_s** (*String*) String representation of the object in this format: *artist - song*.
|
181
149
|
- **artist** (*Artist*) The artist.
|
182
150
|
- **album** (*Album*) The album.
|
183
151
|
|
184
152
|
### Artist
|
185
153
|
|
186
|
-
Methods available for the `Artist` class.
|
154
|
+
Methods available for the `Spot::Artist` class.
|
187
155
|
|
188
156
|
- **name** (*String*) Name of the artist.
|
189
157
|
- **to_s** (*String*) Same as above.
|
190
158
|
|
191
159
|
### Album
|
192
160
|
|
193
|
-
Methods available for the `Album` class.
|
161
|
+
Methods available for the `Spot::Album` class.
|
194
162
|
|
195
163
|
- **artist** (*Artist*) The artist.
|
196
164
|
|
197
165
|
### Spot
|
198
166
|
|
199
|
-
This one is easier to explain in plain code.
|
200
|
-
|
201
167
|
```` ruby
|
202
|
-
spot = Spot.find_song("kaizers orchestra")
|
168
|
+
spot = Spot::Search.find_song("kaizers orchestra")
|
203
169
|
|
204
170
|
puts spot.num_results # => 188
|
205
171
|
puts spot.limit # => 100
|
@@ -209,8 +175,8 @@ puts spot.query # => "kaizers orchestra"
|
|
209
175
|
|
210
176
|
- **num_results** (*Fixnum*) The amount of hits.
|
211
177
|
- **limit** (*Fixnum*) The amount of results on each page.
|
212
|
-
- **offset** (*Fixnum*) Unknown.
|
213
178
|
- **query** (*String*) The search param that was passed to Spotify.
|
179
|
+
- **offset** (*Fixnum*)
|
214
180
|
|
215
181
|
## Request limit!
|
216
182
|
|
@@ -218,8 +184,8 @@ puts spot.query # => "kaizers orchestra"
|
|
218
184
|
Which means that you can't just use it like this.
|
219
185
|
|
220
186
|
```` ruby
|
221
|
-
["song1", "song2" ...
|
222
|
-
Spot.find_song(song)
|
187
|
+
["song1", "song2", ...].each do |song|
|
188
|
+
Spot::Search.find_song(song)
|
223
189
|
# Do something with the data.
|
224
190
|
end
|
225
191
|
````
|
@@ -234,7 +200,7 @@ require "spot"
|
|
234
200
|
wires = []
|
235
201
|
["song1", "song2" ... ].each do |s|
|
236
202
|
wires << Wire.new(max: 10, wait: 1, vars: [s]) do |song|
|
237
|
-
Spot.find_song(song)
|
203
|
+
Spot::Search.find_song(song)
|
238
204
|
# Do something with the data.
|
239
205
|
end
|
240
206
|
end
|
@@ -248,7 +214,7 @@ wires.map(&:join)
|
|
248
214
|
|
249
215
|
## Requirements
|
250
216
|
|
251
|
-
*Spot* is tested in *OS X 10.6.7* using Ruby *1.8.7*, *1.9.2*.
|
217
|
+
*Spot* is tested in *OS X 10.6.7, 10.7.4* using Ruby *1.8.7*, *1.9.2*.
|
252
218
|
|
253
219
|
## License
|
254
220
|
|
data/lib/spot.rb
CHANGED
@@ -2,214 +2,209 @@
|
|
2
2
|
require "spot/song"
|
3
3
|
require "spot/artist"
|
4
4
|
require "spot/album"
|
5
|
-
require "
|
5
|
+
require "spot/clean"
|
6
|
+
require "json"
|
6
7
|
require "rest-client"
|
7
|
-
require "uri"
|
8
8
|
require "levenshteinish"
|
9
|
-
require "
|
10
|
-
require "iconv"
|
9
|
+
require "charlock_holmes/string"
|
11
10
|
require "yaml"
|
11
|
+
require "uri"
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
:
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
:
|
28
|
-
|
29
|
-
|
13
|
+
module Spot
|
14
|
+
class Search
|
15
|
+
def initialize
|
16
|
+
@methods = {
|
17
|
+
:artists => {
|
18
|
+
:selector => :artists,
|
19
|
+
:class => Spot::Artist,
|
20
|
+
:url => generate_url("artist")
|
21
|
+
},
|
22
|
+
:songs => {
|
23
|
+
:selector => :tracks,
|
24
|
+
:class => Spot::Song,
|
25
|
+
:url => generate_url("track")
|
26
|
+
},
|
27
|
+
:albums => {
|
28
|
+
:selector => :albums,
|
29
|
+
:class => Spot::Album,
|
30
|
+
:url => generate_url("album")
|
31
|
+
}
|
30
32
|
}
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
33
|
+
|
34
|
+
@cache = {}
|
35
|
+
|
36
|
+
@exclude = YAML.load(File.read("#{File.dirname(__FILE__)}/spot/ignore.yml"))
|
37
|
+
|
38
|
+
@config = {
|
39
|
+
:exclude => 2,
|
40
|
+
:popularity => 7,
|
41
|
+
:limit => 0.7,
|
42
|
+
:offset => 10
|
43
|
+
}
|
44
|
+
|
45
|
+
@options = {}
|
46
|
+
end
|
43
47
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
def self.method_missing(method, *args, &blk)
|
48
|
-
Spot.new.send(method, *args, &blk)
|
49
|
-
end
|
50
|
-
|
51
|
-
def method_missing(method, *args, &blk)
|
52
|
-
if method.to_s =~ /^find(_all)?_([a-z]+)$/i
|
53
|
-
find($2, !!$1, args.first)
|
54
|
-
elsif scrape and content["info"].keys.include?(method.to_s)
|
55
|
-
content["info"][method.to_s]
|
56
|
-
else
|
57
|
-
super(method, *args, &blk)
|
48
|
+
def self.method_missing(method, *args, &blk)
|
49
|
+
Spot::Search.new.send(method, *args, &blk)
|
58
50
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
def prime
|
66
|
-
tap { @prime = true }
|
67
|
-
end
|
68
|
-
|
69
|
-
def prefix(value)
|
70
|
-
tap { @prefix = value }
|
71
|
-
end
|
72
|
-
|
73
|
-
def find(type, all, s)
|
74
|
-
tap {
|
75
|
-
@search = s
|
76
|
-
@type = all ? type.to_sym : "#{type}s".to_sym
|
77
|
-
raise NoMethodError.new(@type) unless @methods.keys.include?(@type)
|
78
|
-
}
|
79
|
-
end
|
80
|
-
|
81
|
-
def results
|
82
|
-
@_results ||= scrape
|
83
|
-
end
|
84
|
-
|
85
|
-
def strip
|
86
|
-
tap { @strip = true }
|
87
|
-
end
|
88
|
-
|
89
|
-
def territory(value)
|
90
|
-
tap { @options.merge!(:territory => value) }
|
91
|
-
end
|
92
|
-
|
93
|
-
def result
|
94
|
-
@prime ? results.sort_by do |res|
|
95
|
-
res.popularity
|
96
|
-
end.reverse[0..4].map do |r|
|
97
|
-
song, artist = type_of(r)
|
98
|
-
|
99
|
-
match = "#{song} #{artist}".split(" ")
|
100
|
-
raw = clean!(search).split(" ")
|
101
|
-
|
102
|
-
if raw.length < match.length
|
103
|
-
diff = match - raw
|
104
|
-
res = diff.length.to_f/match.length
|
51
|
+
|
52
|
+
def method_missing(method, *args, &blk)
|
53
|
+
if method.to_s =~ /^find(_all)?_([a-z]+)$/i
|
54
|
+
find($2, !!$1, args.first)
|
55
|
+
elsif scrape and content["info"].keys.include?(method.to_s)
|
56
|
+
content["info"][method.to_s]
|
105
57
|
else
|
106
|
-
|
107
|
-
res = diff.length.to_f/raw.length
|
58
|
+
super(method, *args, &blk)
|
108
59
|
end
|
109
|
-
|
110
|
-
if diff.length > 1 and not match.map{ |m| diff.include?(m) }.all?
|
111
|
-
res =+ diff.map do |value|
|
112
|
-
match.map do |m|
|
113
|
-
Levenshtein.distance(value, m)
|
114
|
-
end.inject(:+)
|
115
|
-
end.inject(:+) / @config[:offset]
|
116
|
-
end
|
117
|
-
|
118
|
-
[res - r.popularity/@config[:popularity], r]
|
119
|
-
end.reject do |distance, value|
|
120
|
-
exclude?(value.to_s) or not value.valid?
|
121
|
-
end.sort_by do |distance, _|
|
122
|
-
distance
|
123
|
-
end.map(&:last).first : results.first
|
124
|
-
end
|
125
|
-
|
126
|
-
def type_of(r)
|
127
|
-
if @type == :songs
|
128
|
-
return r.name.to_s.downcase, r.artist.name.to_s.downcase
|
129
|
-
elsif @type == :artists
|
130
|
-
return r.song.title.to_s.downcase, r.name.to_s.downcase
|
131
|
-
else
|
132
|
-
return "", r.artist.to_s.downcase
|
133
60
|
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def clean!(string)
|
137
|
-
string.strip!
|
138
61
|
|
139
|
-
|
140
|
-
|
141
|
-
# Song - A & abc def => Song - A
|
142
|
-
# Song - A "abc def" => Song - A
|
143
|
-
# Song - A [B + C] => Song - A
|
144
|
-
# Song A B.mp3 => Song A B
|
145
|
-
# Song a.b.c.d.e => Song a b c d e
|
146
|
-
# 10. Song => Song
|
147
|
-
[/\.[a-z0-9]{2,3}$/, /\[[^\]]*\]/,/".*"/, /'.*'/, /[&|\/|\+][^\z]*/, /^(\d+.*?[^a-z]+?)/i].each do |reg|
|
148
|
-
string = string.gsub(reg, '').strip
|
62
|
+
def page(value)
|
63
|
+
tap { @page = value }
|
149
64
|
end
|
150
65
|
|
151
|
-
|
152
|
-
|
66
|
+
def prime
|
67
|
+
tap { @prime = true }
|
153
68
|
end
|
154
69
|
|
155
|
-
|
156
|
-
|
70
|
+
def prefix(value)
|
71
|
+
tap { @prefix = value }
|
157
72
|
end
|
158
73
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
@exclude.map { |value| !! compare.match(/#{value}/i) }.any?
|
166
|
-
end
|
167
|
-
|
168
|
-
private
|
169
|
-
def url
|
170
|
-
@url ||= @methods[@type][:url].
|
171
|
-
gsub(/<SEARCH>/, URI.escape(search)).
|
172
|
-
gsub(/<PAGE>/, (@page || 1).to_s)
|
74
|
+
def find(type, all, s)
|
75
|
+
tap {
|
76
|
+
@search = s
|
77
|
+
@type = all ? type.to_sym : "#{type}s".to_sym
|
78
|
+
raise NoMethodError.new(@type) unless @methods.keys.include?(@type)
|
79
|
+
}
|
173
80
|
end
|
174
81
|
|
175
|
-
def
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
82
|
+
def results
|
83
|
+
unless @prime
|
84
|
+
@_results ||= scrape.select(&:valid?)
|
85
|
+
else
|
86
|
+
@_results ||= scrape
|
87
|
+
end
|
180
88
|
end
|
181
89
|
|
182
|
-
def
|
183
|
-
|
184
|
-
|
185
|
-
@cache[@type] = []; content[@methods[@type][:selector].to_s].each do |item|
|
186
|
-
item = @methods[@type][:class].new(item.merge(@options))
|
187
|
-
@cache[@type] << item if item.valid? or @prime
|
188
|
-
end
|
189
|
-
@cache[@type]
|
90
|
+
def strip
|
91
|
+
tap { @strip = true }
|
190
92
|
end
|
191
93
|
|
192
|
-
def
|
193
|
-
|
194
|
-
cd = CharDet.detect(data)
|
195
|
-
data = cd.confidence > 0.6 ? Iconv.conv(cd.encoding, "UTF-8", data) : data
|
196
|
-
@content ||= JSON.parse(data)
|
94
|
+
def territory(value)
|
95
|
+
tap { @options.merge!(:territory => value) }
|
197
96
|
end
|
198
97
|
|
199
|
-
def
|
200
|
-
@
|
98
|
+
def result
|
99
|
+
@prime ? results.sort_by do |res|
|
100
|
+
res.popularity
|
101
|
+
end.reverse[0..6].map do |r|
|
102
|
+
song, artist = type_of(r)
|
103
|
+
|
104
|
+
match = clean!([song, artist].join(" ")).split(/\s+/)
|
105
|
+
raw = clean!(search).split(/\s+/)
|
106
|
+
|
107
|
+
if raw.length < match.length
|
108
|
+
diff = match - raw
|
109
|
+
res = diff.length.to_f/match.length
|
110
|
+
else
|
111
|
+
diff = raw - match
|
112
|
+
res = diff.length.to_f/raw.length
|
113
|
+
end
|
114
|
+
|
115
|
+
if diff.length > 1 and not match.map{ |m| diff.include?(m) }.all?
|
116
|
+
res =+ diff.map do |value|
|
117
|
+
match.map do |m|
|
118
|
+
Levenshtein.distance(value, m)
|
119
|
+
end.inject(:+)
|
120
|
+
end.inject(:+) / @config[:offset]
|
121
|
+
end
|
122
|
+
|
123
|
+
[res - r.popularity/@config[:popularity], r]
|
124
|
+
end.reject do |distance, song|
|
125
|
+
exclude?(song.to_s) or not song.valid?
|
126
|
+
end.sort_by do |distance, _|
|
127
|
+
distance
|
128
|
+
end.map(&:last).first : results.select(&:valid?).first
|
201
129
|
end
|
202
130
|
|
203
|
-
def
|
204
|
-
|
205
|
-
|
206
|
-
|
131
|
+
def type_of(r)
|
132
|
+
if @type == :songs
|
133
|
+
return r.name.to_s.downcase, r.artist.name.to_s.downcase
|
134
|
+
elsif @type == :artists
|
135
|
+
return r.song.title.to_s.downcase, r.name.to_s.downcase
|
207
136
|
else
|
208
|
-
|
137
|
+
return "", r.artist.to_s.downcase
|
209
138
|
end
|
210
139
|
end
|
211
140
|
|
212
|
-
def
|
213
|
-
|
141
|
+
def exclude?(compare)
|
142
|
+
@exclude.
|
143
|
+
reject{ |value| @search.to_s.match(/#{value}/i) }.
|
144
|
+
map{ |value| compare.match(/#{value}/i) }.any?
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# @value String To be cleaned
|
149
|
+
# @return String A cleaned string
|
150
|
+
#
|
151
|
+
def clean!(value)
|
152
|
+
Spot::Clean.new(value).process
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
def url
|
157
|
+
@url ||= @methods[@type][:url].
|
158
|
+
gsub(/<SEARCH>/, URI.escape(search)).
|
159
|
+
gsub(/<PAGE>/, (@page || 1).to_s)
|
160
|
+
end
|
161
|
+
|
162
|
+
def search(force = false)
|
163
|
+
return @_search if @_search
|
164
|
+
@_search = ""
|
165
|
+
@_search = ((@strip or force) ? clean!(@prefix) + " " : @prefix + " ") if @prefix
|
166
|
+
@_search += ((@strip or force) ? clean!(@search) : @search)
|
167
|
+
end
|
168
|
+
|
169
|
+
def scrape
|
170
|
+
return @cache[@type] if @cache[@type]
|
171
|
+
|
172
|
+
@cache[@type] = []; content[@methods[@type][:selector].to_s].each do |item|
|
173
|
+
item = @methods[@type][:class].new(item.merge(@options))
|
174
|
+
@cache[@type] << item if item.valid? or @prime
|
175
|
+
end
|
176
|
+
@cache[@type]
|
177
|
+
end
|
178
|
+
|
179
|
+
def content
|
180
|
+
data = download
|
181
|
+
|
182
|
+
if encoding = data.detect_encoding[:encoding]
|
183
|
+
data = download.force_encoding(encoding)
|
184
|
+
else
|
185
|
+
data = download.strip
|
186
|
+
end
|
187
|
+
|
188
|
+
JSON.parse(data)
|
189
|
+
rescue ArgumentError
|
190
|
+
JSON.parse(download)
|
191
|
+
end
|
192
|
+
|
193
|
+
def download
|
194
|
+
@download ||= RestClient.get(url, :timeout => 10)
|
195
|
+
end
|
196
|
+
|
197
|
+
def errors(error)
|
198
|
+
case error.to_s
|
199
|
+
when "403 Forbidden"
|
200
|
+
raise Spot::RequestLimitError.new(url)
|
201
|
+
else
|
202
|
+
raise error
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def generate_url(type)
|
207
|
+
"http://ws.spotify.com/search/1/#{type}.json?q=<SEARCH>&page=<PAGE>"
|
208
|
+
end
|
214
209
|
end
|
215
210
|
end
|