classifieds_api 0.1.0 → 1.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.
- checksums.yaml +4 -4
- data/.gitignore +6 -1
- data/README.rdoc +9 -0
- data/Rakefile +8 -0
- data/classifieds_api.gemspec +3 -2
- data/lib/classifieds_api.rb +4 -2
- data/lib/classifieds_api/ad.rb +115 -0
- data/lib/classifieds_api/kijiji.rb +322 -0
- data/lib/classifieds_api/search_filters.rb +134 -0
- data/lib/classifieds_api/version.rb +1 -1
- metadata +23 -5
- data/README.md +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 714c4645bfb468ff19cb1512775b8933d91c3fb6
|
4
|
+
data.tar.gz: b3d558c70659613ce0cfad26b8dcb63296c16bd6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b4c19a643adf817a6dd7fb9a8333772cb466d7f88c8cbe31ee7e2e2ed39411565c6c4a38ee8d80b5078976377ef691a9879e1d25ff41430e84f550e5bb069bf
|
7
|
+
data.tar.gz: 8e5623695ce780cf04cd8f47ffb34645a3d0d130784f7fc778a96a177e50066d6ec1563ec82a1e84b4f0cce5eec2f6473d408ce7baf758605881331249aff0ab
|
data/.gitignore
CHANGED
data/README.rdoc
ADDED
data/Rakefile
CHANGED
data/classifieds_api.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Jean-Francois Bisson"]
|
10
10
|
spec.email = ["jbisson@uwaterloo.ca"]
|
11
11
|
|
12
|
-
spec.summary = %q{
|
13
|
-
spec.description = %q{Simple
|
12
|
+
spec.summary = %q{Simple API for popular classifieds website}
|
13
|
+
spec.description = %q{Simple API for popular classifieds website. Currently, the gem allows you to do search items on Kijiji in Ontario.}
|
14
14
|
spec.homepage = "https://github.com/jfbisson4/classifieds_api"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
|
25
25
|
spec.add_development_dependency "bundler", "~> 1.8"
|
26
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
spec.add_development_dependency "test-unit", "~> 3.0"
|
27
28
|
|
28
29
|
spec.add_runtime_dependency "nokogiri", "~> 1.6"
|
29
30
|
end
|
data/lib/classifieds_api.rb
CHANGED
@@ -0,0 +1,115 @@
|
|
1
|
+
module ClassifiedsApi
|
2
|
+
class Ad
|
3
|
+
attr_reader :title, :date_listed, :price, :address, :description,
|
4
|
+
:image_urls, :offering, :featured, :url
|
5
|
+
|
6
|
+
def initialize(title, date_listed, price, address, description, image_urls,
|
7
|
+
offering, featured, url)
|
8
|
+
self.title = title
|
9
|
+
self.date_listed = date_listed
|
10
|
+
self.price = price
|
11
|
+
self.address = address
|
12
|
+
self.description = description
|
13
|
+
self.image_urls = image_urls
|
14
|
+
self.offering = offering
|
15
|
+
self.featured = featured
|
16
|
+
self.url = url
|
17
|
+
end
|
18
|
+
|
19
|
+
def title=(title)
|
20
|
+
unless title.is_a? String
|
21
|
+
raise TypeError, "Argument #{title} is not a string."
|
22
|
+
end
|
23
|
+
@title = title
|
24
|
+
end
|
25
|
+
|
26
|
+
def date_listed=(date_listed)
|
27
|
+
if date_listed.nil? == false
|
28
|
+
unless date_listed.is_a? DateTime
|
29
|
+
raise TypeError, "Argument #{date_listed} is not a DateTime."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@date_listed = date_listed
|
33
|
+
end
|
34
|
+
|
35
|
+
def price=(price)
|
36
|
+
if price.nil? == false
|
37
|
+
unless price.is_a? Numeric
|
38
|
+
raise TypeError, "Argument #{price} is not a numeric."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
@price = price
|
42
|
+
end
|
43
|
+
|
44
|
+
def address=(address)
|
45
|
+
if address.nil? == false
|
46
|
+
unless address.is_a? String
|
47
|
+
raise TypeError, "Argument #{address} is not a string."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
@address = address
|
51
|
+
end
|
52
|
+
|
53
|
+
def description=(description)
|
54
|
+
unless description.is_a? String
|
55
|
+
raise TypeError, "Argument #{description} is not a string."
|
56
|
+
end
|
57
|
+
@description = description
|
58
|
+
end
|
59
|
+
|
60
|
+
def image_urls=(image_urls)
|
61
|
+
if image_urls.nil? == false
|
62
|
+
unless image_urls.is_a? Array
|
63
|
+
raise TypeError, "Argument #{image_urls} is not a string."
|
64
|
+
end
|
65
|
+
end
|
66
|
+
@image_urls = image_urls
|
67
|
+
end
|
68
|
+
|
69
|
+
def offering=(offering)
|
70
|
+
unless !!offering == offering
|
71
|
+
raise TypeError "Argument #{offering} is not a string."
|
72
|
+
end
|
73
|
+
@offering = offering
|
74
|
+
end
|
75
|
+
|
76
|
+
def featured=(featured)
|
77
|
+
unless !!featured == featured
|
78
|
+
raise TypeError "Argument #{featured} is not a boolean."
|
79
|
+
end
|
80
|
+
@featured = featured
|
81
|
+
end
|
82
|
+
|
83
|
+
def url=(url)
|
84
|
+
unless url.is_a? String
|
85
|
+
raise TypeError, "Argument #{address} is no a string."
|
86
|
+
end
|
87
|
+
@url = url
|
88
|
+
end
|
89
|
+
|
90
|
+
def print_ad
|
91
|
+
puts '-------------------------------------------------------------------'
|
92
|
+
if @offering == true
|
93
|
+
print "Offering - "
|
94
|
+
else
|
95
|
+
print "Wanted - "
|
96
|
+
end
|
97
|
+
if @featured_ad == true
|
98
|
+
puts "Featured Ad"
|
99
|
+
else
|
100
|
+
puts "Regular Ad"
|
101
|
+
end
|
102
|
+
if (@date_listed.nil? == false)
|
103
|
+
puts "Date Listed: #{@date_listed.strftime("%d/%m/%Y %H:%M")}"
|
104
|
+
else
|
105
|
+
puts "Date Listed:"
|
106
|
+
end
|
107
|
+
puts "Title: #{@title}"
|
108
|
+
puts "Price: #{@price}"
|
109
|
+
puts "Description: #{@description}"
|
110
|
+
puts "Address: #{@address}"
|
111
|
+
puts "URL: #{@url}"
|
112
|
+
puts '-------------------------------------------------------------------'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,322 @@
|
|
1
|
+
module ClassifiedsApi
|
2
|
+
class Kijiji
|
3
|
+
|
4
|
+
def search(keyword, search_limit, search_filters)
|
5
|
+
|
6
|
+
unless keyword.is_a? String
|
7
|
+
raise TypeError, "Argument #{keyword} is not a string."
|
8
|
+
end
|
9
|
+
unless search_limit.is_a? Numeric
|
10
|
+
raise TypeError, "Argument #{search_limit} is not a numeric."
|
11
|
+
end
|
12
|
+
if search_limit <= 0
|
13
|
+
raise RangeError, "Argument #{search_limit} should be bigger than 0."
|
14
|
+
end
|
15
|
+
if search_limit > SEARCH_LIMIT
|
16
|
+
raise RangeError,
|
17
|
+
"Argument #{search_limit} should be smaller than the search limit " +
|
18
|
+
"of #{SEARCH_LIMIT}."
|
19
|
+
end
|
20
|
+
unless search_filters.instance_of? SearchFilters
|
21
|
+
raise TypeError,
|
22
|
+
"The search filters argument is not of the SearchFilters class."
|
23
|
+
end
|
24
|
+
|
25
|
+
ads = []
|
26
|
+
page_number = 1
|
27
|
+
until ads.count >= search_limit
|
28
|
+
|
29
|
+
search_page_url = get_search_page_url(keyword,
|
30
|
+
page_number,
|
31
|
+
search_filters)
|
32
|
+
search_page_html = get_search_page_html(search_page_url)
|
33
|
+
noko_search_html = get_nokogiri_html(search_page_html)
|
34
|
+
noko_search_results = get_noko_results_on_search_page(
|
35
|
+
noko_search_html,
|
36
|
+
search_filters.include_sponsored_ads)
|
37
|
+
|
38
|
+
if noko_search_results.count == 0
|
39
|
+
return ads
|
40
|
+
end
|
41
|
+
|
42
|
+
noko_search_results.each do |noko_result|
|
43
|
+
if ads.count >= search_limit
|
44
|
+
return ads
|
45
|
+
end
|
46
|
+
|
47
|
+
ad_title_wanted = get_title (noko_result)
|
48
|
+
ad_title = remove_wanted (ad_title_wanted)
|
49
|
+
ad_price = get_price (noko_result)
|
50
|
+
ad_short_description = get_short_description (noko_result)
|
51
|
+
ad_offering = get_offering (ad_title_wanted)
|
52
|
+
ad_featured = get_ad_featured (noko_result)
|
53
|
+
ad_url = get_ad_url (noko_result)
|
54
|
+
|
55
|
+
|
56
|
+
new_ad = Ad.new(ad_title,
|
57
|
+
nil,
|
58
|
+
ad_price,
|
59
|
+
nil,
|
60
|
+
ad_short_description,
|
61
|
+
nil,
|
62
|
+
ad_offering,
|
63
|
+
ad_featured,
|
64
|
+
ad_url)
|
65
|
+
|
66
|
+
ads.push(new_ad)
|
67
|
+
end
|
68
|
+
page_number += 1
|
69
|
+
end
|
70
|
+
return ads
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def get_search_page_url(keyword, page_number, search_filters)
|
76
|
+
category_key = get_category_key (search_filters.category)
|
77
|
+
category_id = get_category_id (search_filters.category)
|
78
|
+
page_key = get_page_key (page_number)
|
79
|
+
location_string_key = get_location_string_key (search_filters.location)
|
80
|
+
location_id = get_location_id (search_filters.location)
|
81
|
+
distance_key = get_distance_key (search_filters.distance)
|
82
|
+
offer_type_key = get_offer_type_key (search_filters.offer_type)
|
83
|
+
price_key = get_price_key(search_filters.price_minimum,
|
84
|
+
search_filters.price_maximum,
|
85
|
+
offer_type_key)
|
86
|
+
|
87
|
+
|
88
|
+
url = ""
|
89
|
+
url += "http://www.kijiji.ca/b-"
|
90
|
+
url += "#{category_key}"
|
91
|
+
url += "#{location_string_key}/"
|
92
|
+
url += "#{keyword}/"
|
93
|
+
url += "#{page_key}"
|
94
|
+
url += "#{category_id}"
|
95
|
+
url += "#{location_id}"
|
96
|
+
url += "#{distance_key}"
|
97
|
+
url += "#{offer_type_key}"
|
98
|
+
url += "#{price_key}"
|
99
|
+
return url
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_search_page_html(page_url)
|
103
|
+
html = ""
|
104
|
+
open(page_url) {|io| html = io.read}
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_nokogiri_html(html)
|
108
|
+
noko_html = Nokogiri::HTML(html)
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_noko_results_on_search_page(noko_html, include_sponsored_ads)
|
112
|
+
noko_regular_ads = noko_html.css('table.regular-ad')
|
113
|
+
if include_sponsored_ads == true
|
114
|
+
noko_featured_ads = noko_html.css('table.top-feature')
|
115
|
+
noko_ads = noko_featured_ads + noko_regular_ads
|
116
|
+
else
|
117
|
+
noko_ads = noko_regular_ads
|
118
|
+
end
|
119
|
+
return noko_ads
|
120
|
+
end
|
121
|
+
|
122
|
+
def get_title(noko_result)
|
123
|
+
title = noko_result.css('td.description').css('a.title').text.strip
|
124
|
+
end
|
125
|
+
|
126
|
+
def remove_wanted(title_wanted)
|
127
|
+
title = title_wanted.gsub('Wanted:', '').strip
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_price(noko_result)
|
131
|
+
string_price =
|
132
|
+
noko_result.css('td.price').text.strip.gsub('$', '').gsub(',', '')
|
133
|
+
price = string_price.to_f
|
134
|
+
if string_price == "Free"
|
135
|
+
return 0
|
136
|
+
elsif string_price == "Please Contact" || string_price == ""
|
137
|
+
return nil
|
138
|
+
else
|
139
|
+
return price
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_short_description(noko_result)
|
144
|
+
description = noko_result.css('td.description').css('p').text.strip
|
145
|
+
end
|
146
|
+
|
147
|
+
def get_offering(title_wanted)
|
148
|
+
if title_wanted.include? 'Wanted:'
|
149
|
+
return false
|
150
|
+
else
|
151
|
+
return true
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def get_ad_featured(noko_result)
|
156
|
+
css_class = get_node_class(noko_result)
|
157
|
+
if (css_class.include? 'regular-ad')
|
158
|
+
return false
|
159
|
+
else
|
160
|
+
return true
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def get_node_class(noko_result)
|
165
|
+
css_class = noko_result['class'].strip
|
166
|
+
end
|
167
|
+
|
168
|
+
def get_ad_url(noko_result)
|
169
|
+
extension = noko_result.css('td.description').css('a.title')[0]["href"]
|
170
|
+
url = "http://www.kijiji.ca" + extension
|
171
|
+
end
|
172
|
+
|
173
|
+
def get_category_key(category)
|
174
|
+
if category == 'all'
|
175
|
+
return ""
|
176
|
+
else
|
177
|
+
return "#{CATEGORY_KEYS[category]}/"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def get_category_id(category)
|
182
|
+
return CATEGORY_IDS[category]
|
183
|
+
end
|
184
|
+
|
185
|
+
def get_page_key(page_number)
|
186
|
+
if (page_number > 1)
|
187
|
+
return "page-#{page_number}/"
|
188
|
+
else
|
189
|
+
return ""
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def get_location_string_key(location)
|
194
|
+
if location == 'Toronto (GTA)'
|
195
|
+
return 'gta-greater-toronto-area'
|
196
|
+
end
|
197
|
+
location_string = location.dup
|
198
|
+
location_string.gsub! '/', ''
|
199
|
+
location_string.gsub! '.', ''
|
200
|
+
location_string.downcase!
|
201
|
+
location_string.split(' ').join('-')
|
202
|
+
end
|
203
|
+
|
204
|
+
def get_location_id(location)
|
205
|
+
location_id = LOCATION_IDS[location]
|
206
|
+
end
|
207
|
+
|
208
|
+
def get_distance_key(distance)
|
209
|
+
if distance != 50
|
210
|
+
return "r#{distance}"
|
211
|
+
else
|
212
|
+
return ""
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def get_offer_type_key(offer_type)
|
217
|
+
offer_key = ""
|
218
|
+
if offer_type == 'Offering'
|
219
|
+
offer_key = "?ad=offering"
|
220
|
+
elsif offer_type == 'Wanted'
|
221
|
+
offer_key = '?ad=wanted'
|
222
|
+
end
|
223
|
+
return offer_key
|
224
|
+
end
|
225
|
+
|
226
|
+
def get_price_key(min_price, max_price, previous_param)
|
227
|
+
price_key = ""
|
228
|
+
if min_price != 0 || max_price != 10000000
|
229
|
+
if previous_param != ""
|
230
|
+
price_key += "&price="
|
231
|
+
else
|
232
|
+
price_key += "?price="
|
233
|
+
end
|
234
|
+
if min_price != 0
|
235
|
+
price_key += "#{min_price}"
|
236
|
+
end
|
237
|
+
price_key += "__"
|
238
|
+
if max_price != 10000000
|
239
|
+
price_key += "#{max_price}"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
return price_key
|
243
|
+
end
|
244
|
+
|
245
|
+
SEARCH_LIMIT = 10
|
246
|
+
|
247
|
+
CATEGORY_KEYS = Hash[ 'buy and sell', 'buy-sell',
|
248
|
+
'services', 'services',
|
249
|
+
'cars & vehicles', 'cars-vehicles',
|
250
|
+
'pets', 'pets',
|
251
|
+
'vacation rentals', 'vacation-rentals',
|
252
|
+
'community', 'community',
|
253
|
+
'real estate', 'real-estate',
|
254
|
+
'jobs', 'jobs',
|
255
|
+
'resumes', 'resumes']
|
256
|
+
|
257
|
+
|
258
|
+
CATEGORY_IDS = Hash['all', 'k0l',
|
259
|
+
'buy and sell', 'k0c10l',
|
260
|
+
'services', 'k0c72l',
|
261
|
+
'cars & vehicles', 'k0c27l',
|
262
|
+
'pets', 'k0c112l',
|
263
|
+
'vacation rentals', 'k0c800l',
|
264
|
+
'community', 'k0c1l',
|
265
|
+
'real estate', 'k0c34l',
|
266
|
+
'jobs', 'k0c45l',
|
267
|
+
'resumes', 'k0c218l']
|
268
|
+
|
269
|
+
LOCATION_IDS = Hash['Berrie', '1700006',
|
270
|
+
'Belleville Area', '1700129',
|
271
|
+
'Belleville', '1700130',
|
272
|
+
'Trenton', '1700132',
|
273
|
+
'Brantford', '1700206',
|
274
|
+
'Brockville', '1700247',
|
275
|
+
'Chatham-Kent', '1700239',
|
276
|
+
'Cornwall', '1700133',
|
277
|
+
'Guelph', '1700242',
|
278
|
+
'Hamilton', '80014',
|
279
|
+
'Kapuskasing', '1700237',
|
280
|
+
'Kenora', '1700249',
|
281
|
+
'Kingston Area', '1700181',
|
282
|
+
'Kingston', '1700183',
|
283
|
+
'Napanee', '1700182',
|
284
|
+
'Kitchener Area', '1700209',
|
285
|
+
'Cambridge', '1700210',
|
286
|
+
'Kitchener / Waterloo', '1700212',
|
287
|
+
'Stratford', '1700213',
|
288
|
+
'Leamington', '1700240',
|
289
|
+
'London', '1700214',
|
290
|
+
'Muskoka', '1700078',
|
291
|
+
'Norfolk County', '1700248',
|
292
|
+
'North Bay', '1700243',
|
293
|
+
'Ottawa / Gatineau Area', '1700184',
|
294
|
+
'Gatineau', '1700186',
|
295
|
+
'Ottawa', '1700185',
|
296
|
+
'Owen Sound', '1700187',
|
297
|
+
'Peterborough Area', '1700217',
|
298
|
+
'Kawartha Lakes', '1700219',
|
299
|
+
'Peterborough', '1700218',
|
300
|
+
'Renfrew County Area', '1700074',
|
301
|
+
'Pembroke', '1700075',
|
302
|
+
'Petawawa', '1700076',
|
303
|
+
'Renfrew', '1700077',
|
304
|
+
'Sarnia Area', '1700189',
|
305
|
+
'Grand Bend', '1700190',
|
306
|
+
'Sarnia', '1700191',
|
307
|
+
'Sault Ste. Marie', '1700244',
|
308
|
+
'St. Catharines', '80016',
|
309
|
+
'Sudbury', '1700245',
|
310
|
+
'Thunder Bay', '1700126',
|
311
|
+
'Timmins', '1700238',
|
312
|
+
'Toronto (GTA)', '1700272',
|
313
|
+
'City of Toronto', '1700273',
|
314
|
+
'Markham / York Region', '1700274',
|
315
|
+
'Mississauga / Peel Region', '1700276',
|
316
|
+
'Oakville / Halton Region', '1700277',
|
317
|
+
'Oshawa / Durham Region', '1700275',
|
318
|
+
'Windsor Region', '1700220',
|
319
|
+
'Woodstock', '1700241']
|
320
|
+
|
321
|
+
end
|
322
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module ClassifiedsApi
|
2
|
+
class SearchFilters
|
3
|
+
|
4
|
+
attr_reader :category, :location, :distance, :offer_type, :price_minimum,
|
5
|
+
:price_maximum, :additional_info, :include_sponsored_ads
|
6
|
+
|
7
|
+
def initialize(params={})
|
8
|
+
self.category = params[:category] || 'all'
|
9
|
+
self.location = params[:location]
|
10
|
+
self.distance = params[:distance] || 50
|
11
|
+
self.offer_type = params[:offer_type] || 'all'
|
12
|
+
self.price_minimum = params[:price_minimum] || 0
|
13
|
+
self.price_maximum = params[:price_maximum] || 10000000
|
14
|
+
self.include_sponsored_ads = params[:include_sponsored_ads] || false
|
15
|
+
end
|
16
|
+
|
17
|
+
def category=(category)
|
18
|
+
unless CATEGORIES.include? category
|
19
|
+
#print_list('CATEGORIES', CATEGORIES)
|
20
|
+
raise ArgumentError,
|
21
|
+
"Argument #{category} is not a valid category."
|
22
|
+
end
|
23
|
+
@category = category
|
24
|
+
end
|
25
|
+
|
26
|
+
def location=(location)
|
27
|
+
unless LOCATIONS.include? location
|
28
|
+
#print_list('LOCATIONS', LOCATIONS)
|
29
|
+
raise ArgumentError,
|
30
|
+
"Argument #{location} is not a valid location."
|
31
|
+
end
|
32
|
+
@location = location
|
33
|
+
end
|
34
|
+
|
35
|
+
def distance=(distance)
|
36
|
+
unless distance.is_a? Numeric
|
37
|
+
raise TypeError, "Argument #{distance} is not numeric."
|
38
|
+
end
|
39
|
+
if 0 > distance || distance > 500
|
40
|
+
raise RangeError, "Argument #{distance} should be between 0 and 500."
|
41
|
+
end
|
42
|
+
@distance = distance
|
43
|
+
end
|
44
|
+
|
45
|
+
def offer_type=(offer_type)
|
46
|
+
unless OFFER_TYPES.include? offer_type
|
47
|
+
#print_list('OFFER TYPES', OFFER_TYPES)
|
48
|
+
raise ArgumentError,
|
49
|
+
"Argument #{offer_type} is not a valid offer type."
|
50
|
+
end
|
51
|
+
@offer_type = offer_type
|
52
|
+
end
|
53
|
+
|
54
|
+
def price_minimum=(price_minimum)
|
55
|
+
unless price_minimum.is_a? Numeric
|
56
|
+
raise TypeError, "Argument #{price_minimum} is not numeric."
|
57
|
+
end
|
58
|
+
if 0 > price_minimum
|
59
|
+
raise RangeError,
|
60
|
+
"Argument #{price_minimum} should be between bigger than 0."
|
61
|
+
end
|
62
|
+
if price_minimum > (@price_maximum || 10000000)
|
63
|
+
raise RangeError,
|
64
|
+
"Argument #{price_minimum} should be smaller than maximum price " +
|
65
|
+
"#{@price_maximum}."
|
66
|
+
end
|
67
|
+
@price_minimum = price_minimum
|
68
|
+
end
|
69
|
+
|
70
|
+
def price_maximum=(price_maximum)
|
71
|
+
unless price_maximum.is_a? Numeric
|
72
|
+
raise TypeError, "Argument #{price_maximum} is not numeric."
|
73
|
+
end
|
74
|
+
if price_maximum < (@price_minimum || 0)
|
75
|
+
raise RangeError,
|
76
|
+
"Argument #{price_maximum} should be bigger than minimum price " +
|
77
|
+
"#{@price_minimum}."
|
78
|
+
end
|
79
|
+
@price_maximum = price_maximum
|
80
|
+
end
|
81
|
+
|
82
|
+
def include_sponsored_ads=(include_sponsored_ads)
|
83
|
+
unless !!include_sponsored_ads == include_sponsored_ads
|
84
|
+
raise TypeError, "Argument #{include_sponsored_ads} is not a boolean."
|
85
|
+
end
|
86
|
+
@include_sponsored_ads = include_sponsored_ads
|
87
|
+
end
|
88
|
+
|
89
|
+
def print_filters
|
90
|
+
puts '-------------------------------------------------------------------'
|
91
|
+
puts 'Search Filters:'
|
92
|
+
puts "Category: #{@category}"
|
93
|
+
puts "Location: #{@location}"
|
94
|
+
puts "Distance: #{@distance}"
|
95
|
+
puts "Offer Type: #{@offer_type}"
|
96
|
+
puts "Minimum Price: #{@price_minimum}"
|
97
|
+
puts "Maximum Price: #{@price_maximum}"
|
98
|
+
puts "Include Sponsored Ads: #{@include_sponsored_ads}"
|
99
|
+
puts '-------------------------------------------------------------------'
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def print_list(list_name, list)
|
105
|
+
puts '-------------------------------------------------------------------'
|
106
|
+
puts "#{list_name}:"
|
107
|
+
list.each do |element|
|
108
|
+
puts element
|
109
|
+
end
|
110
|
+
puts '-------------------------------------------------------------------'
|
111
|
+
end
|
112
|
+
|
113
|
+
CATEGORIES = ['all', 'buy and sell', 'services', 'cars & vehicles', 'pets',
|
114
|
+
'vacation rentals', 'community', 'real estate', 'jobs',
|
115
|
+
'resumes']
|
116
|
+
|
117
|
+
LOCATIONS = ['Berrie', 'Belleville Area', 'Belleville', 'Trenton',
|
118
|
+
'Brantford', 'Brockville', 'Chatham-Kent', 'Cornwall',
|
119
|
+
'Guelph', 'Hamilton', 'Kapuskasing', 'Kenora', 'Kingston Area',
|
120
|
+
'Kingston', 'Napanee', 'Kitchener Area', 'Cambridge',
|
121
|
+
'Kitchener / Waterloo', 'Stratford', 'Leamington', 'London',
|
122
|
+
'Muskoka', 'Norfolk County', 'North Bay',
|
123
|
+
'Ottawa / Gatineau Area', 'Gatineau', 'Ottawa', 'Owen Sound',
|
124
|
+
'Peterborough Area', 'Kawartha Lakes', 'Peterborough',
|
125
|
+
'Renfrew County Area', 'Pembroke', 'Petawawa', 'Renfrew',
|
126
|
+
'Sarnia Area', 'Grand Bend', 'Sarnia', 'Sault Ste. Marie',
|
127
|
+
'St. Catharines', 'Sudbury', 'Thunder Bay', 'Timmins',
|
128
|
+
'Toronto (GTA)', 'City of Toronto', 'Markham / York Region',
|
129
|
+
'Mississauga / Peel Region', 'Oakville / Halton Region',
|
130
|
+
'Oshawa / Durham Region', 'Windsor Region', 'Woodstock']
|
131
|
+
|
132
|
+
OFFER_TYPES = ['all', 'Offering', 'Wanted']
|
133
|
+
end
|
134
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: classifieds_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean-Francois Bisson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: nokogiri
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,7 +66,8 @@ dependencies:
|
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '1.6'
|
55
|
-
description: Simple
|
69
|
+
description: Simple API for popular classifieds website. Currently, the gem allows
|
70
|
+
you to do search items on Kijiji in Ontario.
|
56
71
|
email:
|
57
72
|
- jbisson@uwaterloo.ca
|
58
73
|
executables: []
|
@@ -63,12 +78,15 @@ files:
|
|
63
78
|
- ".travis.yml"
|
64
79
|
- Gemfile
|
65
80
|
- LICENSE
|
66
|
-
- README.
|
81
|
+
- README.rdoc
|
67
82
|
- Rakefile
|
68
83
|
- bin/console
|
69
84
|
- bin/setup
|
70
85
|
- classifieds_api.gemspec
|
71
86
|
- lib/classifieds_api.rb
|
87
|
+
- lib/classifieds_api/ad.rb
|
88
|
+
- lib/classifieds_api/kijiji.rb
|
89
|
+
- lib/classifieds_api/search_filters.rb
|
72
90
|
- lib/classifieds_api/version.rb
|
73
91
|
homepage: https://github.com/jfbisson4/classifieds_api
|
74
92
|
licenses: []
|
@@ -93,5 +111,5 @@ rubyforge_project:
|
|
93
111
|
rubygems_version: 2.4.5
|
94
112
|
signing_key:
|
95
113
|
specification_version: 4
|
96
|
-
summary:
|
114
|
+
summary: Simple API for popular classifieds website
|
97
115
|
test_files: []
|
data/README.md
DELETED