spot 0.1.4 → 2.0.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/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
|