peekapp 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.
@@ -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
+