peekapp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ test.rb
5
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in peekapp.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ peekapp (0.1.0)
5
+ curb (>= 0.7.9)
6
+ json (>= 1.4.6)
7
+ nokogiri (>= 1.4.4)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ curb (0.7.10)
13
+ json (1.5.0)
14
+ nokogiri (1.4.4)
15
+
16
+ PLATFORMS
17
+ ruby
18
+
19
+ DEPENDENCIES
20
+ peekapp!
@@ -0,0 +1,42 @@
1
+ Retrieve reviews and ratings from the App Store.
2
+
3
+ # Installation
4
+ gem install peekapp
5
+
6
+ ## How to
7
+
8
+ For advanced functionnalities you should read the source. Peekapp can only retrieve ratings & reviews from the Canadian App Store (*this will be fixed soon*).
9
+
10
+ require "peekapp"
11
+
12
+ # Get the App
13
+ app = Peekapp::Apps::find 390574988 # App ID
14
+
15
+ # Get reviews for this App
16
+ app.reviews
17
+ # => [#<Review:0x102237a28 @data={
18
+ # :title => "Amazing",
19
+ # :comment => "Best Twitter app on the face of the planet!",
20
+ # :username => "Thomas Gallagher",
21
+ # :rating => 5,
22
+ # :user_id => 33308895,
23
+ # :version => "2.0",
24
+ # :date => "9-Oct-2009",
25
+ # :id => 131605495
26
+ # }>, ...]
27
+
28
+ # And the ratings...
29
+ app.ratings
30
+ # => [#Rating:0x1029372a23 @data={
31
+ # :current => { "1": 38, "2": 12, "3": 23, "4": 25, "5": 105 },
32
+ # :all => { "1": 2736, "2": 749, "3": 1045, "4": 1103, "5": 3880 },
33
+ # :store_id => "143455-5,12"
34
+ # }>, ...]
35
+
36
+ ## Warning
37
+ Since Peekapp is **scraping** the App Store, you might experience some problems if Apple change iTunes' html layout. I've made some tests and the App Store is updated every 20 minutes (*ballpark*). So don't waste your time trying to get *real time* ratings & reviews.
38
+
39
+ ## Todo
40
+ - Create unit tests w/ [FakeWeb](https://github.com/chrisk/fakeweb)
41
+ - Allow international App Stores
42
+ - iTunes Connect support
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,10 @@
1
+ require "yaml"
2
+ require "curb"
3
+ require "json"
4
+ require "nokogiri"
5
+
6
+ require "peekapp/base.rb"
7
+ require "peekapp/version.rb"
8
+ require "peekapp/apps.rb"
9
+ require "peekapp/reviews.rb"
10
+ require "peekapp/ratings.rb"
@@ -0,0 +1,46 @@
1
+ module Peekapp
2
+
3
+ module Apps
4
+
5
+ def self.search query # {{{
6
+ result = JSON.parse(Peekapp::query(:url => $peekapp_config[:search_url], :keywords => query.gsub(' ', '%20')))
7
+ result['results'].map { |a| App.new a }
8
+ end # }}}
9
+
10
+ def self.find id # {{{
11
+ result = JSON.parse(Peekapp::query :url => $peekapp_config[:app_url], :app_id => id)
12
+ raise AppNotFound if result["resultCount"] < 1
13
+ result["results"].map{ |a| App.new a }.first
14
+ end # }}}
15
+
16
+ end
17
+
18
+ class App
19
+
20
+ attr_accessor :_reviews, :_ratings
21
+
22
+ def initialize data # {{{
23
+ @data = data
24
+ end # }}}
25
+
26
+ def id # {{{
27
+ @data["trackId"]
28
+ end # }}}
29
+
30
+ def method_missing method # {{{
31
+ @data[method.to_s]
32
+ end # }}}
33
+
34
+ def ratings options={} # {{{
35
+ self._ratings = Peekapp::Ratings.from_app self.id, ["143455-5,12"], options if self._ratings.nil? or options[:force_refresh]
36
+ self._ratings
37
+ end # }}}
38
+
39
+ def reviews options={} # {{{
40
+ self._reviews = Peekapp::Reviews.from_app self.id, ["143455-5,12"], options if self._reviews.nil? or options[:force_refresh]
41
+ self._reviews
42
+ end # }}}
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,26 @@
1
+ $peekapp_config = YAML::load(File.open("#{File.dirname(__FILE__)}/config/default.yml"))
2
+
3
+ module Peekapp
4
+
5
+ def self.query args # {{{
6
+ c = Curl::Easy.perform(parse_url(args)) do |request|
7
+ request.headers["User-Agent"] = $peekapp_config[:user_agent]
8
+ request.headers["X-Apple-Store-Front"] = "#{args[:store_id]}" if args[:store_id]
9
+ end
10
+ c.body_str
11
+ end # }}}
12
+
13
+ def self.parse_url data # {{{
14
+ url = data[:url]
15
+ data.each_pair{|k,v| url = url.gsub("|#{k}|", v.to_s) if k != :url}
16
+ url
17
+ end # }}}
18
+
19
+ def self.load_exceptions # {{{
20
+ exceptions = YAML::load(File.open("#{File.dirname(__FILE__)}/config/exceptions.yml"))
21
+ exceptions.each { |error| self.module_eval("#{error} = Class.new(StandardError)") }
22
+ end # }}}
23
+
24
+ end
25
+
26
+ Peekapp::load_exceptions
@@ -0,0 +1,5 @@
1
+ :user_agent: "iTunes/10.0.1 (Macintosh; Intel Mac OS X 10.6.4) AppleWebKit/533.18.1"
2
+ :reviews_url: "http://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/customerReviews?update=1&id=|app_id|&appVersion=|app_version|&sort=4&displayable-kind=11&page=|page|"
3
+ :ratings_url: "http://itunes.apple.com/WebObjects/MZStore.woa/wa/customerReviews?id=|app_id|&displayable-kind=11"
4
+ :app_url: "http://ax.itunes.apple.com/WebObjects/MZStoreServices.woa/wa/wsLookup?id=|app_id|"
5
+ :search_url: "http://ax.itunes.apple.com/WebObjects/MZStoreServices.woa/wa/wsSearch?term=|keywords|&entity=software"
@@ -0,0 +1,4 @@
1
+ - AppNotFound
2
+ - ReviewsUnavailableForThisApp
3
+ - LatestReviewReached
4
+
@@ -0,0 +1,66 @@
1
+ module Peekapp
2
+
3
+ module Ratings
4
+
5
+ def self.from_app id, stores, options = {} # {{{
6
+ rating = Array.new
7
+ stores.each do |s|
8
+ rating << parse({
9
+ :dom => Peekapp::query({
10
+ :url => $peekapp_config[:ratings_url],
11
+ :page => 1,
12
+ :app_id => id,
13
+ :store_id => s,
14
+ :app_version => (options[:app_version] ? options[:app_version] : "all")
15
+ }),
16
+ :store_id => s
17
+ })
18
+ end
19
+ rating
20
+ end # }}}
21
+
22
+ def self.parse data # {{{
23
+ rating = Rating.new :store_id => data[:store_id]
24
+ dom = Nokogiri::HTML.parse(data[:dom])
25
+ nb_ratings_section = dom.css("div.ratings-histogram").count
26
+
27
+ dom.css("div.ratings-histogram").each_with_index do |r,i|
28
+ data = Hash.new
29
+ result = true
30
+ r.css("div.vote").each_with_index{ |v,j| data.merge!({(5-j) => v.css("span.total").children.to_s.to_i }) }
31
+
32
+ if nb_ratings_section === 1 or i === 1
33
+ rating.set :key => :all, :value => data
34
+ rating.set :key => :current, :value => data if nb_ratings_section === 1
35
+ else
36
+ rating.set :key => :current, :value => data
37
+ end
38
+ end
39
+
40
+ rating.set :key => :all, :value => {1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0} if nb_ratings_section < 1
41
+ rating
42
+ end # }}}
43
+
44
+ end
45
+
46
+ class Rating
47
+
48
+ def initialize data # {{{
49
+ @data = data
50
+ end # }}}
51
+
52
+ def id # {{{
53
+ @data[:id]
54
+ end # }}}
55
+
56
+ def set args # {{{
57
+ @data.merge!({args[:key] => args[:value]})
58
+ end # }}}
59
+
60
+ def method_missing method # {{{
61
+ @data[method.to_sym]
62
+ end # }}}
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,75 @@
1
+ module Peekapp
2
+
3
+ module Reviews
4
+
5
+ def self.from_app id, stores, options = {} # {{{
6
+ reviews = Array.new
7
+ begin
8
+ stores.each do |s|
9
+ args = {
10
+ :url => $peekapp_config[:reviews_url],
11
+ :app_id => id,
12
+ :store_id => s,
13
+ :page => (options[:page] ? options[:page] : 1),
14
+ :app_version => (options[:app_version] ? options[:app_version] : "all")
15
+ }
16
+ dom = Peekapp::query args
17
+ begin
18
+ nb_page = Nokogiri::HTML.parse(dom).css("div.paginated-content").first["total-number-of-pages"].to_i
19
+ rescue
20
+ raise ReviewsUnavailableForThisApp
21
+ end
22
+ nb_page.times do |z|
23
+ # dom is already instanciated for z === 0
24
+ args[:page] = z+1
25
+ dom = Peekapp::query args if z > 0
26
+ parse(dom).each do |p|
27
+ raise LatestReviewReached if p.id === options[:latest_review_id].to_s
28
+ reviews << p
29
+ end
30
+ end
31
+ end
32
+ rescue LatestReviewReached
33
+ # Do nothing... just get out.
34
+ end
35
+ reviews
36
+ end # }}}
37
+
38
+ def self.parse data # {{{
39
+ reviews = Array.new
40
+ dom = Nokogiri::HTML.parse(data)
41
+ dom.css("div.customer-review").each do |r|
42
+ # That's some ugly stuff... I know
43
+ reviews << Review.new({
44
+ :id => r.css("a.report").first["href"].split("=").last,
45
+ :title => r.css("span.customerReviewTitle").children.to_s,
46
+ :comment => r.css("p.content").children.to_s.gsub("\n", "").gsub(" ", ""),
47
+ :username => r.css("a.reviewer").children.to_s.gsub("\n", "").gsub(" ", ""),
48
+ :user_id => r.css("a.reviewer").first["href"].split('=').last,
49
+ :rating => r.css("div.rating").first['aria-label'].split(' ').first.to_i,
50
+ :date => r.css("span.user-info").children.to_s.split(" -\n").last.gsub("\n", "").gsub(" ", ""),
51
+ :version => r.css("span.user-info").children.to_s.split(" -\n")[1].split(" - ").first.split(" ").last.to_s,
52
+ })
53
+ end
54
+ reviews
55
+ end # }}}
56
+
57
+ end
58
+
59
+ class Review
60
+
61
+ def initialize data # {{{
62
+ @data = data
63
+ end # }}}
64
+
65
+ def id # {{{
66
+ @data[:id]
67
+ end # }}}
68
+
69
+ def method_missing method # {{{
70
+ @data[method.to_sym]
71
+ end # }}}
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,3 @@
1
+ module Peekapp
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "peekapp/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "peekapp"
7
+ s.version = Peekapp::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Samuel Garneau"]
10
+ s.email = ["samgarneau@gmail.com"]
11
+ s.homepage = "http://github.com/garno/peekapp"
12
+ s.summary = %q{Retrieve ratings & reviews from the App Store}
13
+ s.description = %q{Easily scrape the App Store to retrieve ratings & reviews.}
14
+
15
+ #s.rubyforge_project = "peekapp"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency('nokogiri', '>= 1.4.4')
23
+ s.add_dependency('json', '>= 1.4.6')
24
+ s.add_dependency('curb', '>= 0.7.9')
25
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: peekapp
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Samuel Garneau
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-17 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: nokogiri
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 15
30
+ segments:
31
+ - 1
32
+ - 4
33
+ - 4
34
+ version: 1.4.4
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: json
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 11
46
+ segments:
47
+ - 1
48
+ - 4
49
+ - 6
50
+ version: 1.4.6
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: curb
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 17
62
+ segments:
63
+ - 0
64
+ - 7
65
+ - 9
66
+ version: 0.7.9
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ description: Easily scrape the App Store to retrieve ratings & reviews.
70
+ email:
71
+ - samgarneau@gmail.com
72
+ executables: []
73
+
74
+ extensions: []
75
+
76
+ extra_rdoc_files: []
77
+
78
+ files:
79
+ - .gitignore
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - README.markdown
83
+ - Rakefile
84
+ - lib/peekapp.rb
85
+ - lib/peekapp/apps.rb
86
+ - lib/peekapp/base.rb
87
+ - lib/peekapp/config/default.yml
88
+ - lib/peekapp/config/exceptions.yml
89
+ - lib/peekapp/ratings.rb
90
+ - lib/peekapp/reviews.rb
91
+ - lib/peekapp/version.rb
92
+ - peekapp.gemspec
93
+ has_rdoc: true
94
+ homepage: http://github.com/garno/peekapp
95
+ licenses: []
96
+
97
+ post_install_message:
98
+ rdoc_options: []
99
+
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ hash: 3
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 3
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ requirements: []
121
+
122
+ rubyforge_project:
123
+ rubygems_version: 1.3.7
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Retrieve ratings & reviews from the App Store
127
+ test_files: []
128
+