popularity 0.0.1

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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +21 -0
  5. data/Gemfile.lock +126 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.rdoc +92 -0
  8. data/Rakefile +54 -0
  9. data/VERSION +1 -0
  10. data/lib/.DS_Store +0 -0
  11. data/lib/popularity.rb +92 -0
  12. data/lib/popularity/crawler.rb +76 -0
  13. data/lib/popularity/facebook.rb +26 -0
  14. data/lib/popularity/github.rb +28 -0
  15. data/lib/popularity/google_plus.rb +22 -0
  16. data/lib/popularity/medium.rb +33 -0
  17. data/lib/popularity/pinterest.rb +21 -0
  18. data/lib/popularity/reddit_comment.rb +32 -0
  19. data/lib/popularity/reddit_post.rb +47 -0
  20. data/lib/popularity/reddit_share.rb +72 -0
  21. data/lib/popularity/results_container.rb +70 -0
  22. data/lib/popularity/search.rb +79 -0
  23. data/lib/popularity/soundcloud.rb +44 -0
  24. data/lib/popularity/twitter.rb +21 -0
  25. data/popularity.gemspec +124 -0
  26. data/spec/cassettes/facebook.yml +50 -0
  27. data/spec/cassettes/github-valid.yml +105 -0
  28. data/spec/cassettes/google_plus.yml +89 -0
  29. data/spec/cassettes/medium-valid.yml +110 -0
  30. data/spec/cassettes/pinterest.yml +44 -0
  31. data/spec/cassettes/reddit-comment.yml +207 -0
  32. data/spec/cassettes/reddit-post.yml +174 -0
  33. data/spec/cassettes/reddit.yml +183 -0
  34. data/spec/cassettes/result-container-test.yml +97 -0
  35. data/spec/cassettes/search-multi.yml +1211 -0
  36. data/spec/cassettes/search.yml +598 -0
  37. data/spec/cassettes/soundcloud-valid.yml +682 -0
  38. data/spec/cassettes/twitter.yml +64 -0
  39. data/spec/cassettes/unknown-reddit-post.yml +68 -0
  40. data/spec/facebook_spec.rb +23 -0
  41. data/spec/github_spec.rb +39 -0
  42. data/spec/google_plus_spec.rb +27 -0
  43. data/spec/medium_spec.rb +47 -0
  44. data/spec/pinterest_spec.rb +27 -0
  45. data/spec/reddit_comment_spec.rb +60 -0
  46. data/spec/reddit_post_spec.rb +64 -0
  47. data/spec/reddit_spec.rb +89 -0
  48. data/spec/results_container_spec.rb +61 -0
  49. data/spec/search_spec.rb +79 -0
  50. data/spec/soundcloud_spec.rb +66 -0
  51. data/spec/spec_helper.rb +95 -0
  52. data/spec/twitter_spec.rb +19 -0
  53. data/test/facebook_test.rb +28 -0
  54. data/test/fixtures/vcr_cassettes/facebook.yml +50 -0
  55. data/test/fixtures/vcr_cassettes/google.yml +425 -0
  56. data/test/helper.rb +34 -0
  57. data/test/test_non_specific.rb +27 -0
  58. data/test/test_twitter.rb +28 -0
  59. metadata +242 -0
@@ -0,0 +1,26 @@
1
+ module Popularity
2
+ class Facebook < Crawler
3
+ def shares
4
+ response_json['shares'].to_f.to_i
5
+ end
6
+
7
+ def comments
8
+ response_json['comments'].to_f.to_i
9
+ end
10
+
11
+ def as_json(options = {})
12
+ { "shares" => shares,
13
+ "comments" => comments }
14
+ end
15
+
16
+ def total
17
+ shares + comments
18
+ end
19
+
20
+ protected
21
+
22
+ def request_url
23
+ "http://graph.facebook.com/?id=#{@url}"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module Popularity
2
+ class Github < Crawler
3
+ def stars
4
+ response_json.size
5
+ end
6
+
7
+ def as_json(options = {})
8
+ { "stars" => stars }
9
+ end
10
+
11
+ def total
12
+ response_json.size
13
+ end
14
+
15
+ def valid?
16
+ host == 'github.com'
17
+ end
18
+
19
+ protected
20
+
21
+ def request_url
22
+ parts = @url.split("/").last(2)
23
+ repo = parts.last
24
+ owner = parts.first
25
+ "https://api.github.com/repos/#{owner}/#{repo}/stargazers"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ module Popularity
2
+ class GooglePlus < Crawler
3
+ def plus_ones
4
+ matches = response.scan(/window.__SSR = {c\: (\d+.\d+E?\d+)/)
5
+ matches.flatten.first.to_f.to_i
6
+ end
7
+
8
+ def as_json(options = {})
9
+ {"plus_ones" => plus_ones}
10
+ end
11
+
12
+ def total
13
+ plus_ones
14
+ end
15
+
16
+ protected
17
+
18
+ def request_url
19
+ "https://plusone.google.com/_/+1/fastbutton?url=#{URI::encode(@url)}"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,33 @@
1
+ module Popularity
2
+ class Medium < Crawler
3
+ def recommends
4
+ response_json["payload"]["value"]["count"]
5
+ end
6
+
7
+ def as_json(options = {})
8
+ {"recommends" => recommends}
9
+ end
10
+
11
+ def total
12
+ recommends
13
+ end
14
+
15
+ def valid?
16
+ host == 'medium.com'
17
+ end
18
+
19
+ protected
20
+
21
+ def medium_id
22
+ @url.split("/").last.split("-").last
23
+ end
24
+
25
+ def request_url
26
+ "https://medium.com/p/#{medium_id}/upvotes"
27
+ end
28
+
29
+ def response_json
30
+ JSON.parse(response.sub("])}while(1);</x>", ""))
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ module Popularity
2
+ class Pinterest < Crawler
3
+ def pins
4
+ JSON.parse(response.gsub('receiveCount(','').gsub(')',''))['count'].to_f.to_i
5
+ end
6
+
7
+ def as_json(options = {})
8
+ {"pins" => pins}
9
+ end
10
+
11
+ def total
12
+ pins
13
+ end
14
+
15
+ protected
16
+
17
+ def request_url
18
+ "http://api.pinterest.com/v1/urls/count.json?url=#{@url}"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ module Popularity
2
+ class RedditComment < Crawler
3
+ def score
4
+ response_json[1]["data"]["children"][0]["data"]["score"]
5
+ end
6
+
7
+ def as_json(options = {})
8
+ {"score" => score}
9
+ end
10
+
11
+ def total
12
+ score
13
+ end
14
+
15
+ def valid?
16
+ return false unless host == 'reddit.com'
17
+
18
+ path = URI.parse(@url).path
19
+ path.split('/').delete_if { |a| a.empty? }.size == 6
20
+ end
21
+
22
+ def name
23
+ "reddit"
24
+ end
25
+
26
+ protected
27
+
28
+ def request_url
29
+ "#{@url}.json"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ module Popularity
2
+ class RedditPost < Crawler
3
+ def score
4
+ begin
5
+ response_json[0]["data"]["children"][0]["data"]["score"]
6
+ rescue
7
+ 0
8
+ end
9
+ end
10
+
11
+ def comments
12
+ begin
13
+ response_json[0]["data"]["children"][0]["data"]["num_comments"]
14
+ rescue
15
+ 0
16
+ end
17
+ end
18
+
19
+ def as_json(options = {})
20
+ {
21
+ "comments" => comments,
22
+ "score" => score
23
+ }
24
+ end
25
+
26
+ def total
27
+ comments + score
28
+ end
29
+
30
+ def valid?
31
+ return false unless host == 'reddit.com'
32
+
33
+ path = URI.parse(@url).path
34
+ path.split('/').delete_if { |a| a.empty? }.size < 6
35
+ end
36
+
37
+ def name
38
+ "reddit"
39
+ end
40
+
41
+ protected
42
+
43
+ def request_url
44
+ "#{@url}.json"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,72 @@
1
+ module Popularity
2
+ class RedditShare < Crawler
3
+ include Popularity::ContainerMethods
4
+
5
+ class RedditResult < Popularity::RedditPost
6
+ def initialize(url, r)
7
+ super(url)
8
+ @response = r
9
+
10
+ self
11
+ end
12
+
13
+ def has_response?
14
+ true
15
+ end
16
+
17
+ def valid?
18
+ URI.parse(@url).host
19
+ end
20
+
21
+ def fetch
22
+ false
23
+ end
24
+
25
+ def fetch_async
26
+ false
27
+ end
28
+ end
29
+
30
+ def initialize(*args)
31
+ super(*args)
32
+ posts_json = response_json["data"]["children"]
33
+ posts_json.each do |child|
34
+ new_json = response_json.clone
35
+
36
+ new_json["data"]["children"] = [child]
37
+ url = "http://reddit.com#{child["data"]["permalink"]}"
38
+ post = RedditResult.new(url, JSON.dump([new_json]))
39
+
40
+ self.add_result(post)
41
+ end
42
+
43
+ self
44
+ end
45
+
46
+ def to_json(options ={})
47
+ total = {"comments" => 0, "posts" => 0, "score" => 0}
48
+ return total unless @results
49
+
50
+ @results.collect(&:to_json).each do |json|
51
+ json.each do |key, value|
52
+ total[key] ||= 0
53
+ total[key] += value
54
+ end
55
+ end
56
+ total["posts"] = posts
57
+ total
58
+ end
59
+
60
+ def posts
61
+ @results.size rescue 0
62
+ end
63
+
64
+ def name
65
+ "reddit"
66
+ end
67
+
68
+ def request_url
69
+ "http://www.reddit.com/r/search/search.json?q=url:#{@url}"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,70 @@
1
+
2
+ module Popularity
3
+ module ContainerMethods
4
+ def self.included(base)
5
+ base.class_eval do
6
+ def results
7
+ @results
8
+ end
9
+
10
+ def add_result(result)
11
+ @results ||= []
12
+
13
+ if @results.size > 0
14
+ verify_type = @results.first.name
15
+ if verify_type == result.name
16
+ @results << result
17
+ else
18
+ raise "ResultTypeError", "types must be the same within a results container"
19
+ end
20
+ else
21
+ @results << result
22
+ end
23
+ end
24
+
25
+ def to_json(options ={})
26
+ individual = {}
27
+ total = {}
28
+ @results.collect do |result|
29
+ json = result.to_json
30
+ individual[result.url] = json
31
+
32
+ json.each do |key, value|
33
+ next if key == "total"
34
+
35
+ if value.is_a?(Hash)
36
+ # RedditShare breaks out into separate results for each reddit link
37
+ # I'm not a big fan of this hacky stuff here
38
+ value.each do |k,v|
39
+ total[k] ||= 0
40
+ total[k] += v
41
+ end
42
+ else
43
+ total[key] ||= 0
44
+ total[key] += value
45
+ end
46
+ end
47
+ end
48
+
49
+ individual["total"] = total
50
+ individual
51
+ end
52
+
53
+ def method_missing(method_sym, *arguments, &block)
54
+ return 0 unless @results
55
+ collection = @results.collect do |result|
56
+ result.send(method_sym, *arguments)
57
+ end
58
+
59
+ if collection.all? { |t| t.is_a?(Fixnum) }
60
+ collection.reduce(:+)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ class ResultsContainer
68
+ include Popularity::ContainerMethods
69
+ end
70
+ end
@@ -0,0 +1,79 @@
1
+ module Popularity
2
+ class Search
3
+ attr_accessor :info
4
+ attr_accessor :results
5
+ attr_accessor :sources
6
+ attr_reader :total
7
+ attr_reader :url
8
+
9
+ def initialize(url)
10
+ @url = url
11
+ @info = {}
12
+ total_score = []
13
+
14
+ selected_types.each do |network|
15
+ network.fetch_async do |code, body|
16
+ add_result(network)
17
+ begin
18
+ if network.has_response?
19
+ total_score << network.total
20
+ end
21
+ rescue Exception => e
22
+ puts "#{network.name} had an accident"
23
+ puts e.message
24
+ puts e.backtrace.join("\n")
25
+ end
26
+ end
27
+ end
28
+
29
+ loop do
30
+ # we want the requests to be asyncronous, but we don't
31
+ # want gem users to have to deal with async code
32
+ #
33
+ break if selected_types.all? { |network| network.async_done? }
34
+ end
35
+
36
+ @total = total_score.reduce(:+)
37
+ end
38
+
39
+ def to_json(options ={})
40
+ json = {}
41
+ self.results.collect do |result|
42
+ json[result.name.to_s] = result.to_json
43
+ end
44
+
45
+ self.sources.collect do |source|
46
+ json[source.to_s] = self.send(source.to_sym).to_json
47
+ end
48
+
49
+ json["total"] = total
50
+
51
+ json
52
+ end
53
+
54
+ protected
55
+
56
+ def selected_types
57
+ # github.com stats only valid for github urls, etc
58
+ @types ||= Popularity::TYPES.collect { |n|
59
+ network = n.new(@url)
60
+ network if network.valid?
61
+ }.compact
62
+ end
63
+
64
+ def add_result(result)
65
+ self.sources ||= []
66
+ self.results ||= []
67
+ self.results << result
68
+ self.sources << result.name.to_sym
69
+
70
+ self.instance_variable_set "@#{result.name}", result
71
+
72
+ self.define_singleton_method(result.name.to_sym) { result }
73
+ end
74
+
75
+ def to_s
76
+ self.class.name
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,44 @@
1
+ module Popularity
2
+ class Soundcloud < Crawler
3
+ def plays
4
+ response.scan(/\"soundcloud:play_count\" content=\"([0-9]*)\"/).flatten.first.to_f.to_i
5
+ end
6
+
7
+ def likes
8
+ response.scan(/\"soundcloud:like_count\" content=\"([0-9]*)\"/).flatten.first.to_f.to_i
9
+ end
10
+
11
+ def comments
12
+ response.scan(/\"soundcloud:comments_count\" content=\"([0-9]*)\"/).flatten.first.to_f.to_i
13
+ end
14
+
15
+ def downloads
16
+ response.scan(/\"soundcloud:download_count\" content=\"([0-9]*)\"/).flatten.first.to_f.to_i
17
+ end
18
+
19
+ def as_json(options = {})
20
+ {"plays" => plays,
21
+ "likes" => likes,
22
+ "comments" => comments,
23
+ "downloads" => downloads }
24
+ end
25
+
26
+ def total
27
+ plays + likes + downloads + comments
28
+ end
29
+
30
+ def valid?
31
+ host == 'soundcloud.com'
32
+ end
33
+
34
+ protected
35
+
36
+ def response_json
37
+ #not json!
38
+ end
39
+
40
+ def request_url
41
+ @url
42
+ end
43
+ end
44
+ end