mobile_stores 0.1.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 +7 -0
- data/.DS_Store +0 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +32 -0
- data/Gemfile.lock +155 -0
- data/Guardfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +65 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/lib/.DS_Store +0 -0
- data/lib/mobile_stores.rb +24 -0
- data/lib/mobile_stores/amazon_marketplace.rb +70 -0
- data/lib/mobile_stores/app.rb +60 -0
- data/lib/mobile_stores/app_store.rb +56 -0
- data/lib/mobile_stores/black_berry_world.rb +55 -0
- data/lib/mobile_stores/google_play.rb +61 -0
- data/lib/mobile_stores/mobile_store.rb +176 -0
- data/lib/mobile_stores/windows_store.rb +72 -0
- data/spec/amazon_marketplace_spec.rb +53 -0
- data/spec/app_spec.rb +64 -0
- data/spec/app_store_spec.rb +65 -0
- data/spec/blackberry_world_spec.rb +53 -0
- data/spec/google_play_spec.rb +53 -0
- data/spec/mobile_store_spec.rb +56 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/windows_store_spec.rb +63 -0
- data/test/helper.rb +18 -0
- data/test/test_mobile_stores.rb +7 -0
- metadata +272 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
module MobileStores
|
2
|
+
class App
|
3
|
+
|
4
|
+
attr_accessor :title, :application_id, :creator_name, :creator_id, :version, :rating, :description, :category, :icon_url, :view_url, :price, :compatibility, :screenshot_urls
|
5
|
+
|
6
|
+
def initialize(hash = {})
|
7
|
+
hash.each_pair do |key, value|
|
8
|
+
self.send "#{ key }=", value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :store
|
14
|
+
|
15
|
+
# Selects the store for further manipulations.
|
16
|
+
#
|
17
|
+
# App.in(:app_store)
|
18
|
+
#
|
19
|
+
def in(store)
|
20
|
+
store_class = if store.is_a? Symbol
|
21
|
+
MobileStores::STORES[store]
|
22
|
+
elsif store.is_a? String
|
23
|
+
Object.const_get(store) rescue nil
|
24
|
+
elsif store.is_a? Class
|
25
|
+
store
|
26
|
+
end
|
27
|
+
|
28
|
+
raise NotImplementedError, "#{ store } is not implemented yet." if store.nil?
|
29
|
+
|
30
|
+
store_class.new
|
31
|
+
end
|
32
|
+
|
33
|
+
# Selects Apple AppStore for further manipulations.
|
34
|
+
def app_store
|
35
|
+
self.in(AppStore)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Selects Google Play Store for further manipulations.
|
39
|
+
def google_play
|
40
|
+
self.in(GooglePlay)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Selects Windows Store for further manipulations.
|
44
|
+
def windows_store
|
45
|
+
self.in(WindowsStore)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Selects BlackBerry World Store for further manipulations.
|
49
|
+
def blackberry_world
|
50
|
+
self.in(BlackBerryWorld)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Selects BlackBerry World Store for further manipulations.
|
54
|
+
def amazon_marketplace
|
55
|
+
self.in(AmazonMarketplace)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module MobileStores
|
2
|
+
class AppStore
|
3
|
+
include MobileStore
|
4
|
+
|
5
|
+
act_as_json_source
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def self.apps_url(query = nil, count = nil, country = nil)
|
10
|
+
url = "http://itunes.apple.com/search?media=software"
|
11
|
+
url = "#{ url }&term=#{ CGI::escape(query) }" if query
|
12
|
+
url = "#{ url }&limit=#{ count }" if count
|
13
|
+
url = "#{ url }&country=#{ country.alpha2 }"
|
14
|
+
url
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.app_url(id, country)
|
18
|
+
"http://itunes.apple.com/lookup?id=#{ CGI::escape(id) }&country=#{ country.alpha2 }"
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.process_app_json(json)
|
22
|
+
app = self.parse_json(json).first
|
23
|
+
raise "App not found." if app.nil?
|
24
|
+
app
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.process_apps_json(json)
|
28
|
+
self.parse_json(json)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.parse_json(json)
|
32
|
+
if json['results']
|
33
|
+
json['results'].map do |app|
|
34
|
+
App.new({
|
35
|
+
application_id: app['trackId'].to_s.force_encoding('utf-8'),
|
36
|
+
title: app['trackName'].force_encoding('utf-8'),
|
37
|
+
creator_name: app['artistName'].force_encoding('utf-8'),
|
38
|
+
creator_id: app['artistId'].to_s.force_encoding('utf-8'),
|
39
|
+
version: app['version'].to_s.force_encoding('utf-8'),
|
40
|
+
rating: (app['averageUserRating'].to_f || 0).to_s.force_encoding('utf-8').to_f,
|
41
|
+
description: app['description'].force_encoding('utf-8'),
|
42
|
+
category: app['primaryGenreName'].force_encoding('utf-8'),
|
43
|
+
icon_url: app['artworkUrl512'].force_encoding('utf-8'),
|
44
|
+
view_url: app['trackViewUrl'].force_encoding('utf-8'),
|
45
|
+
price: app['price'].to_s.force_encoding('utf-8').to_d,
|
46
|
+
compatibility: app['supportedDevices'].sort,
|
47
|
+
screenshot_urls: app['screenshotUrls'] + app['ipadScreenshotUrls']
|
48
|
+
})
|
49
|
+
end
|
50
|
+
else
|
51
|
+
[]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module MobileStores
|
2
|
+
class BlackBerryWorld
|
3
|
+
include MobileStore
|
4
|
+
|
5
|
+
act_as_json_source
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def self.apps_url(query = nil, count = nil, country)
|
10
|
+
url = "http://appworld.blackberry.com/cas/producttype/apps/listtype/search_listing/category/0/search/#{ CGI::escape(query) }?page=1&model=Imagination%20GPU&countrycode=#{ country.alpha2 }&lang=#{ country.languages.first }"
|
11
|
+
url = "#{ url }&pagesize=#{ count }" if count
|
12
|
+
url
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.app_url(id, country)
|
16
|
+
"http://appworld.blackberry.com/cas/content/#{ id }?model=Imagination%20GPU&countrycode=#{ country.alpha2 }&lang=#{ country.languages.first }"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.process_app_json(json)
|
20
|
+
self.parse_element_json(json)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.process_apps_json(json)
|
24
|
+
self.parse_list_json(json)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.parse_element_json(json)
|
28
|
+
App.new({
|
29
|
+
application_id: json['id'],
|
30
|
+
title: json['name'],
|
31
|
+
creator_name: json['vendorName'],
|
32
|
+
creator_id: json['vendorId'],
|
33
|
+
version: (json['cdDTO']['releaseVersion'] rescue nil),
|
34
|
+
rating: (json['rating'] == -1 ? nil : json['rating']),
|
35
|
+
description: json['description'],
|
36
|
+
category: json['categories'][0]['name'],
|
37
|
+
icon_url: "http://appworld.blackberry.com/webstore/servedimages/#{ json['iconId'] }.png/?t=2",
|
38
|
+
view_url: "http://appworld.blackberry.com/webstore/content/#{ json['id'] }",
|
39
|
+
price: (json['cdDTO']['price'].to_d rescue 0),
|
40
|
+
compatibility: json['cdDTO']['supportedDevices'],
|
41
|
+
screenshot_urls: (json['screenShots'] || []).map{ |s| "http://appworld.blackberry.com/webstore/servedimages/#{ s }.png/?t=11" }
|
42
|
+
})
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.parse_list_json(json)
|
46
|
+
json['ldata'].map do |app|
|
47
|
+
App.new({
|
48
|
+
:application_id => app['id'],
|
49
|
+
:title => app['name'],
|
50
|
+
:icon_url => "http://appworld.blackberry.com/webstore/servedimages/#{ app['iconId'] }.png/?t=2"
|
51
|
+
})
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# GooglePlay doesn't support country based filtering, so every country is
|
2
|
+
# supported by default
|
3
|
+
module MobileStores
|
4
|
+
class GooglePlay
|
5
|
+
include MobileStore
|
6
|
+
|
7
|
+
act_as_html_source
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def self.apps_url(query = nil, count = nil, country = nil)
|
12
|
+
url = "https://play.google.com/store/search?hl=en"
|
13
|
+
url = "#{ url }&q=#{ CGI::escape(query) }" if query
|
14
|
+
url = "#{ url }&start=0&num=#{ count }" if count
|
15
|
+
url
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.app_url(id, country)
|
19
|
+
"https://play.google.com/store/apps/details?id=#{ id }&hl=en"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.process_app_html(doc)
|
23
|
+
self.parse_html_page(doc)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.process_apps_html(doc, count = nil)
|
27
|
+
self.parse_html_list(doc, count)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.parse_html_page(doc)
|
31
|
+
App.new({
|
32
|
+
application_id: doc.css('.details-wrapper')[0]['data-docid'],
|
33
|
+
title: doc.css('.document-title div')[0].inner_text.strip,
|
34
|
+
creator_name: doc.css('.document-subtitle.primary')[0].inner_text.strip,
|
35
|
+
creator_id: doc.css('.document-subtitle.primary')[0].inner_text.strip,
|
36
|
+
version: doc.css('[itemprop=softwareVersion]')[0].inner_text.strip,
|
37
|
+
rating: doc.css('[itemprop=ratingValue]')[0]['content'].to_f,
|
38
|
+
description: doc.css('[itemprop=description] .app-orig-desc')[0].inner_text.strip.gsub(%r{</?[^>]+?>}, ''),
|
39
|
+
category: doc.css('.document-subtitle.category')[0].inner_text.strip,
|
40
|
+
icon_url: doc.css('.cover-image')[0]['src'],
|
41
|
+
view_url: "https://play.google.com/store/apps/details?hl=en&id=#{ doc.css('.details-wrapper')[0]['data-docid'] }",
|
42
|
+
price: doc.css('[itemprop=price]')[0]["content"].to_d,
|
43
|
+
compatibility: (doc.css('.metadata div.content[itemprop=operatingSystems]')[0].inner_text.strip rescue nil),
|
44
|
+
screenshot_urls: doc.css('.full-screenshot').map{ |d| d['src'] }
|
45
|
+
})
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.parse_html_list(doc, count = nil)
|
49
|
+
apps = doc.css('.card-list .card').map do |app|
|
50
|
+
App.new({
|
51
|
+
:application_id => app['data-docid'],
|
52
|
+
:title => app.css('img')[0]['alt'],
|
53
|
+
:icon_url => app.css('img')[0]['src']
|
54
|
+
})
|
55
|
+
end
|
56
|
+
|
57
|
+
count ? apps.take(count) : apps
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require "json"
|
2
|
+
require "net/http"
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
module MobileStores
|
6
|
+
module MobileStore
|
7
|
+
|
8
|
+
module InstanceMethods
|
9
|
+
|
10
|
+
attr_accessor :country
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
# set country to United States as default
|
14
|
+
@country = Country.new(:us)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# Selects store corresponding to country or returns selected country,
|
19
|
+
# if arguments is not specified
|
20
|
+
#
|
21
|
+
# App.in(:app_store).country(:us).find("12345") # returns +App+ object
|
22
|
+
#
|
23
|
+
def country(country = nil)
|
24
|
+
if country
|
25
|
+
@country = Country.new(country)
|
26
|
+
self
|
27
|
+
else
|
28
|
+
@country
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Finds app in store by ID.
|
33
|
+
# Returns nil if app was not found.
|
34
|
+
#
|
35
|
+
# App.in(:app_store).find("12345") # returns +App+ object
|
36
|
+
#
|
37
|
+
def find(id)
|
38
|
+
find!(id) rescue nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Finds app in store by ID.
|
42
|
+
# Raises error if app was not found.
|
43
|
+
#
|
44
|
+
# App.in(:app_store).find("12345") # returns +App+ object
|
45
|
+
#
|
46
|
+
def find!(id)
|
47
|
+
self.class.find_app(id, country)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Searches apps in store by query
|
51
|
+
#
|
52
|
+
# App.in(:app_store).search("angry") # returns array of +App+ objects
|
53
|
+
#
|
54
|
+
def search(query, count = nil)
|
55
|
+
self.class.search_apps(query, count, country)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns true if app exists in store searching by ID,
|
59
|
+
# otherwise returns false
|
60
|
+
def exists?(id)
|
61
|
+
not find(id).nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
module ClassMethods
|
67
|
+
|
68
|
+
def act_as_json_source
|
69
|
+
self.extend(JsonClassMethods)
|
70
|
+
end
|
71
|
+
|
72
|
+
def act_as_html_source
|
73
|
+
self.extend(HtmlClassMethods)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns url for a list of apps.
|
77
|
+
def apps_url(query = nil, count = nil)
|
78
|
+
raise_not_implemented
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns url for a single app.
|
82
|
+
def app_url(id)
|
83
|
+
raise_not_implemented
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Raises +NotImplementedError+ when called.
|
89
|
+
def raise_not_implemented
|
90
|
+
raise NotImplementedError, "#{ self.name.to_s }.#{ caller[0] =~ /`([^']*)'/ and $1 } is not implemented."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
module JsonClassMethods
|
95
|
+
|
96
|
+
# Finds app in store by ID.
|
97
|
+
#
|
98
|
+
# AppStore.find_app("12345") # returns +App+ object
|
99
|
+
#
|
100
|
+
def find_app(id, country)
|
101
|
+
process_app_json(json(app_url(id, country)))
|
102
|
+
end
|
103
|
+
|
104
|
+
# Searches apps in store by query.
|
105
|
+
#
|
106
|
+
# AppStore.search_apps("angry") # returns array of +App+ objects
|
107
|
+
#
|
108
|
+
def search_apps(query, count = nil, country)
|
109
|
+
process_apps_json(json(apps_url(query, count, country)))
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
# Processes json for a list of apps.
|
115
|
+
def process_apps_json(json)
|
116
|
+
raise_not_implemented
|
117
|
+
end
|
118
|
+
|
119
|
+
# Process json for a single app.
|
120
|
+
def process_app_json(json)
|
121
|
+
raise_not_implemented
|
122
|
+
end
|
123
|
+
|
124
|
+
# Fetches json object from url.
|
125
|
+
def json(url)
|
126
|
+
response = Net::HTTP.get_response(URI.parse(url))
|
127
|
+
JSON.parse(response.body)
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
module HtmlClassMethods
|
133
|
+
|
134
|
+
# Finds app in store by ID.
|
135
|
+
#
|
136
|
+
# AppStore.find_app("12345") # returns +App+ object
|
137
|
+
#
|
138
|
+
def find_app(id, country)
|
139
|
+
process_app_html(doc(app_url(id, country)))
|
140
|
+
end
|
141
|
+
|
142
|
+
# Searches apps in store by query.
|
143
|
+
#
|
144
|
+
# AppStore.search_apps("angry") # returns array of +App+ objects
|
145
|
+
#
|
146
|
+
def search_apps(query, count = nil, country)
|
147
|
+
process_apps_html(doc(apps_url(query, count, country)), count)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
# Processes json for a list of apps.
|
153
|
+
def process_apps_html(html, count)
|
154
|
+
raise_not_implemented
|
155
|
+
end
|
156
|
+
|
157
|
+
# Process json for a single app.
|
158
|
+
def process_app_html(html)
|
159
|
+
raise_not_implemented
|
160
|
+
end
|
161
|
+
|
162
|
+
# Fetches json object from url.
|
163
|
+
def doc(url)
|
164
|
+
Nokogiri::HTML(open(url))
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.included(base)
|
170
|
+
base.extend(ClassMethods)
|
171
|
+
end
|
172
|
+
|
173
|
+
include InstanceMethods
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module MobileStores
|
2
|
+
class WindowsStore
|
3
|
+
include MobileStore
|
4
|
+
|
5
|
+
COUNTRY_LANGUAGE = { :ao => 'pt', :ar => 'es', :am => 'en', :au => 'en', :la => 'az', :az => 'tn', :bd => 'en', :be => 'nl', :be => 'fr', :bj => 'fr', :bo => 'es', :br => 'pt', :bf => 'fr', :bi => 'fr', :cm => 'en', :ca => 'en', :ca => 'fr', :cz => 'cs', :cl => 'es', :co => 'es', :cd => 'fr', :cr => 'es', :ci => 'fr', :dk => 'da', :de => 'de', :ec => 'es', :ee => 'et', :sv => 'es', :es => 'es', :es => 'ca', :fr => 'fr', :gh => 'en', :gt => 'es', :gn => 'fr', :ht => 'fr', :hn => 'es', :hk => 'en', :hr => 'hr', :is => 'is', :in => 'en', :id => 'id', :ie => 'en', :it => 'it', :ke => 'en', :kw => 'en', :lv => 'lv', :li => 'de', :lt => 'lt', :mg => 'fr', :hu => 'hu', :mw => 'en', :my => 'ms', :my => 'en', :ml => 'fr', :mx => 'es', :mz => 'pt', :nl => 'nl', :nz => 'en', :ni => 'es', :ne => 'fr', :ng => 'en', :no => 'nb', :at => 'de', :la => 'uz', :uz => 'tn', :pk => 'en', :py => 'es', :pe => 'es', :ph => 'en', :ph => 'il', :pl => 'pl', :pt => 'pt', :do => 'es', :ro => 'ro', :rw => 'fr', :ch => 'de', :sn => 'fr', :al => 'sq', :sl => 'en', :sg => 'en', :si => 'sl', :sk => 'sk', :so => 'en', :za => 'en', :la => 'sr', :cs => 'tn', :ch => 'fr', :fi => 'fi', :se => 'sv', :ch => 'it', :tz => 'en', :td => 'fr', :tg => 'fr', :tr => 'tr', :ug => 'en', :gb => 'en', :us => 'en', :ve => 'es', :vn => 'vi', :zm => 'en', :zw => 'en', :gr => 'el', :by => 'be', :bg => 'bg', :kz => 'kk', :mk => 'mk', :ru => 'ru', :tj => 'ru', :tm => 'ru', :ua => 'uk', :il => 'he', :jo => 'ar', :ae => 'ar', :bh => 'ar', :dz => 'ar', :iq => 'ar', :kw => 'ar', :ma => 'ar', :sa => 'ar', :ye => 'ar', :tn => 'ar', :om => 'ar', :qa => 'ar', :eg => 'ar', :in => 'hi', :th => 'th', :kr => 'ko', :cn => 'zh', :tw => 'zh', :jp => 'ja', :hk => 'zh'
|
6
|
+
}
|
7
|
+
|
8
|
+
act_as_html_source
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def self.apps_url(query = nil, count = nil, country)
|
13
|
+
url = "http://www.windowsphone.com/#{ COUNTRY_LANGUAGE[country.alpha2.downcase.to_sym] }-#{ country.alpha2.downcase }/store/search"
|
14
|
+
url = "#{ url }?q=#{ CGI::escape(query) }" if query
|
15
|
+
URI.encode url
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.app_url(id, country)
|
19
|
+
URI.encode "http://www.windowsphone.com/#{ country.languages.first }-#{ country.alpha2.downcase }/store/app/#{ id }"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.process_app_html(doc)
|
23
|
+
app = self.parse_html_page(doc)
|
24
|
+
raise "App not found." if app.nil?
|
25
|
+
app
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.process_apps_html(doc, count = nil)
|
29
|
+
self.parse_html_list(doc, count)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.parse_html_page(doc)
|
33
|
+
App.new({
|
34
|
+
application_id: doc.css('[itemprop=url]')[0]['content'].match(/[^\/]+\/[^\/]+$/)[0],
|
35
|
+
title: doc.css('[itemprop=name]')[0].inner_text,
|
36
|
+
creator_name: doc.css('[itemprop=publisher]')[0].inner_text,
|
37
|
+
creator_id: doc.css('[itemprop=publisher]')[0].inner_text,
|
38
|
+
version: doc.css('[itemprop=softwareVersion]')[0].inner_text,
|
39
|
+
rating: doc.css('[itemprop=ratingValue]')[0]['content'].to_f,
|
40
|
+
description: doc.css('[itemprop=description]')[0].inner_text.gsub(%r{</?[^>]+?>}, ''),
|
41
|
+
category: (doc.css('[itemprop=applicationCategory]')[0].inner_text.capitalize rescue nil),
|
42
|
+
icon_url: doc.css('[itemprop=image]')[0]['src'],
|
43
|
+
view_url: doc.css('[itemprop=url]')[0]['content'],
|
44
|
+
price: doc.css('[itemprop=price]')[0].inner_text.gsub(/[^0-9.,]/, '').to_d,
|
45
|
+
compatibility: doc.css('[itemprop=operatingSystems]').map{ |o| o.inner_text}.join(', '),
|
46
|
+
screenshot_urls: doc.css('#screenshots a').map{ |d| d['href'] }
|
47
|
+
})
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.parse_html_list(doc, count = nil, page = 1)
|
51
|
+
apps = doc.css('.appList td').map do |app|
|
52
|
+
id_string = app.css('a')[0]['data-ov'].split(';').last
|
53
|
+
App.new({
|
54
|
+
:application_id => id_string.split(' ')[1] + '/' + id_string.split(' ')[0],
|
55
|
+
:title => app.css('a')[1].inner_text,
|
56
|
+
:icon_url => app.css('img')[0]['src']
|
57
|
+
})
|
58
|
+
end
|
59
|
+
|
60
|
+
if count and page == 1
|
61
|
+
while apps.size < count and doc.css('#nextLink').length > 0
|
62
|
+
page += 1
|
63
|
+
doc = Nokogiri::HTML(open("http://www.windowsphone.com#{ doc.css('#nextLink')[0]['href'] }"))
|
64
|
+
apps += self.parse_html_list(doc)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
count ? apps.take(count) : apps
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|