postrank-api 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,29 @@
1
+ # Ruby PostRank API Wrapper
2
+
3
+ PostRank API wrapper for Ruby 1.9.
4
+
5
+ * EventMachine & Fibers under the hood - async friendly.
6
+ * Can be used outside of an EM loop - wrapper will spin up and shut down the reactor on demand.
7
+
8
+ For complete documentation on all endpoints please see [PostRank API Docs](http://apidocs.postrank.com)
9
+
10
+ ## A few simple examples
11
+
12
+ require "postrank-api"
13
+
14
+ api = PostRank::API.new('my-appkey')
15
+
16
+ # map a site to postrank id's + retrieve feed meta data
17
+ igvita = api.feed_info('igvita.com')
18
+
19
+ # grab the latest stories from igvita.com
20
+ feed = api.feed(igvita['id'])
21
+
22
+ # grab the top recent post from igvita.com
23
+ top = api.top_posts(igvita['id'], :num => 1)
24
+
25
+ # lookup the engagement score for the past two days
26
+ eng = api.engagement(igvita['id'], :start => 'yesterday')
27
+
28
+ # lookup social metrics for a url
29
+ metrics = api.metrics('http://www.igvita.com/')
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "postrank-api"
7
+ gemspec.summary = "PostRank API Wrapper"
8
+ gemspec.description = gemspec.summary
9
+ gemspec.email = "ilya@igvita.com"
10
+ gemspec.homepage = "http://github.com/postrank/postrank-api"
11
+ gemspec.authors = ["Ilya Grigorik"]
12
+ gemspec.required_ruby_version = ">= 1.9.1"
13
+ gemspec.add_dependency('eventmachine', '>= 0.12.9')
14
+ gemspec.add_dependency('em-http')
15
+ gemspec.add_dependency('em-synchrony')
16
+ gemspec.rubyforge_project = "postrank-api"
17
+ end
18
+
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
22
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1 @@
1
+ require 'postrank-api/api'
@@ -0,0 +1,140 @@
1
+ require 'em-synchrony'
2
+ require 'em-synchrony/em-http'
3
+
4
+ require 'digest/md5'
5
+ require 'chronic'
6
+ require 'yajl'
7
+
8
+ module PostRank
9
+ class API
10
+ V2_API_BASE = 'http://api.postrank.com/v2'
11
+
12
+ def initialize(appkey)
13
+ @appkey = appkey
14
+ end
15
+
16
+ def feed_info(feeds, opts = {})
17
+ req = {
18
+ :query => {
19
+ :appkey => @appkey,
20
+ :noindex => opts[:noindex] || false,
21
+ },
22
+ :body => build_body(feeds, 'feed')
23
+ }
24
+
25
+ http = post("#{V2_API_BASE}/feed/info", req)
26
+ resp = parse(http.response)
27
+
28
+ resp.key?('items') ? resp['items'] : resp
29
+ end
30
+
31
+ def feed(feed, opts = {})
32
+ req = {
33
+ :query => {
34
+ :appkey => @appkey,
35
+ :level => opts[:level] || 'all',
36
+ :q => opts[:q] || '',
37
+ :num => opts[:num] || 10,
38
+ :start => opts[:start] || 0,
39
+ :id => feed
40
+ }
41
+ }
42
+
43
+ http = get("#{V2_API_BASE}/feed/", req)
44
+ parse(http.response)
45
+ end
46
+
47
+ def top_posts(feed, opts = {})
48
+ req = {
49
+ :query => {
50
+ :appkey => @appkey,
51
+ :q => opts[:q] || '',
52
+ :num => opts[:num] || 10,
53
+ :id => feed
54
+ }
55
+ }
56
+
57
+ http = get("#{V2_API_BASE}/feed/topposts/", req)
58
+ parse(http.response)
59
+ end
60
+
61
+ def feed_engagement(feeds, opts = {})
62
+ opts[:start_time] ||= '1 month ago'
63
+ opts[:end_time] ||= 'today'
64
+ opts[:summary] = true if not opts.key?(:summary)
65
+
66
+ req = {
67
+ :query => {
68
+ :appkey => @appkey,
69
+ :mode => opts[:mode] || 'daily',
70
+ :start_time => Chronic.parse(opts[:start_time]).to_i,
71
+ :end_time => Chronic.parse(opts[:end_time]).to_i
72
+ },
73
+ :body => build_body(feeds, 'feed')
74
+ }
75
+
76
+ req[:query][:summary] = opts[:summary] if opts[:summary]
77
+
78
+ http = post("#{V2_API_BASE}/feed/engagement", req)
79
+ parse(http.response)
80
+ end
81
+
82
+ def metrics(urls, opts = {})
83
+ reverse = {}
84
+ urls = [urls].flatten.map do |url|
85
+ md5 = (url =~ /\w{32}/) ? url : Digest::MD5.hexdigest(url)
86
+ reverse[md5] = url
87
+ md5
88
+ end
89
+
90
+ req = {
91
+ :query => {
92
+ :appkey => @appkey,
93
+ },
94
+ :body => build_body(urls, 'url')
95
+ }
96
+
97
+ http = post("#{V2_API_BASE}/entry/metrics", req)
98
+ parse(http.response).inject({}) do |hash, v|
99
+ hash[reverse[v[0]]] = v[1]
100
+ hash
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def parse(data)
107
+ begin
108
+ Yajl::Parser.parse(data)
109
+ rescue Exception => e
110
+ puts "Failed to parse request:"
111
+ puts e.message
112
+ puts e.backtrace[0, 5].join("\n")
113
+ end
114
+ end
115
+
116
+ def build_body(urls, key)
117
+ [urls].flatten.map { |e| "#{key}[]=#{e}" }.join("&")
118
+ end
119
+
120
+ def post(url, req)
121
+ dispatch(:post, url, req)
122
+ end
123
+
124
+ def get(url, req)
125
+ dispatch(:get, url, req)
126
+ end
127
+
128
+ def dispatch(method, url, req)
129
+ if EM.reactor_running?
130
+ http = EM::HttpRequest.new(url).send(method, req)
131
+ else
132
+ EM.synchrony do
133
+ http = EM::HttpRequest.new(url).send(method, req)
134
+ EM.stop
135
+ end
136
+ end
137
+ http
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,57 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{postrank-api}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ilya Grigorik"]
12
+ s.date = %q{2010-06-03}
13
+ s.description = %q{PostRank API Wrapper}
14
+ s.email = %q{ilya@igvita.com}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ "README.md",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "lib/postrank-api.rb",
23
+ "lib/postrank-api/api.rb",
24
+ "postrank-api.gemspec",
25
+ "spec/api_spec.rb"
26
+ ]
27
+ s.homepage = %q{http://github.com/postrank/postrank-api}
28
+ s.rdoc_options = ["--charset=UTF-8"]
29
+ s.require_paths = ["lib"]
30
+ s.required_ruby_version = Gem::Requirement.new(">= 1.9.1")
31
+ s.rubyforge_project = %q{postrank-api}
32
+ s.rubygems_version = %q{1.3.6}
33
+ s.summary = %q{PostRank API Wrapper}
34
+ s.test_files = [
35
+ "spec/api_spec.rb"
36
+ ]
37
+
38
+ if s.respond_to? :specification_version then
39
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
40
+ s.specification_version = 3
41
+
42
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
43
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.9"])
44
+ s.add_runtime_dependency(%q<em-http>, [">= 0"])
45
+ s.add_runtime_dependency(%q<em-synchrony>, [">= 0"])
46
+ else
47
+ s.add_dependency(%q<eventmachine>, [">= 0.12.9"])
48
+ s.add_dependency(%q<em-http>, [">= 0"])
49
+ s.add_dependency(%q<em-synchrony>, [">= 0"])
50
+ end
51
+ else
52
+ s.add_dependency(%q<eventmachine>, [">= 0.12.9"])
53
+ s.add_dependency(%q<em-http>, [">= 0"])
54
+ s.add_dependency(%q<em-synchrony>, [">= 0"])
55
+ end
56
+ end
57
+
@@ -0,0 +1,150 @@
1
+ require 'spec'
2
+ require 'lib/postrank-api'
3
+ require 'pp'
4
+
5
+ describe PostRank::API do
6
+ IGVITA = '421df2d86ab95100de7dcc2e247a08ab'
7
+ EVERBURNING = 'cb3e81ac96fb0ada1212dfce4f329474'
8
+
9
+ let(:api) { PostRank::API.new('test') }
10
+
11
+ it "should initialize with appkey" do
12
+ lambda {
13
+ PostRank::API.new('test')
14
+ }.should_not raise_error
15
+ end
16
+
17
+ describe "FeedInfo API" do
18
+ it "should query for feed info" do
19
+ igvita = api.feed_info('igvita.com')
20
+
21
+ igvita.class.should == Hash
22
+ igvita['tags'].class.should == Array
23
+ igvita['xml'].should match(/igvita/)
24
+ end
25
+
26
+ it "should query for feed info for multiple feeds" do
27
+ feeds = api.feed_info(['igvita.com', 'everburning.com'])
28
+ feeds.class.should == Array
29
+ feeds.size.should == 2
30
+ end
31
+
32
+ it "should return feed info data in-order" do
33
+ feeds = api.feed_info(['igvita.com', 'everburning.com'])
34
+ feeds.class.should == Array
35
+ feeds.first['xml'].should match('igvita.com')
36
+ end
37
+ end
38
+
39
+ describe "Feed API" do
40
+ it "should retrieve content of a feed" do
41
+ igvita = api.feed_info('igvita.com')
42
+ feed = api.feed(igvita['id'])
43
+
44
+ feed.class.should == Hash
45
+ feed['meta']['title'].should match(/igvita/)
46
+ feed['items'].size.should == 10
47
+ end
48
+
49
+ it "should retrieve 1 entry from a feed" do
50
+ EM.synchrony do
51
+ feed = api.feed(IGVITA, :num => 1)
52
+
53
+ feed.class.should == Hash
54
+ feed['meta']['title'].should match(/igvita/)
55
+ feed['items'].size.should == 1
56
+
57
+ EM.stop
58
+ end
59
+ end
60
+
61
+ it "should retrieve entries matching a query" do
62
+ EM.synchrony do
63
+ feed = api.feed(IGVITA, :q => 'abrakadabra')
64
+
65
+ feed.class.should == Hash
66
+ feed['meta']['title'].should match(/igvita/)
67
+ feed['items'].size.should == 0
68
+
69
+ EM.stop
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "Top Posts API" do
75
+ it "should fetch top posts for a feed" do
76
+ EM.synchrony do
77
+ feed = api.top_posts(IGVITA, :num => 1)
78
+
79
+ feed.class.should == Hash
80
+ feed['meta']['title'].should match(/igvita/)
81
+ feed['items'].size.should == 1
82
+
83
+ EM.stop
84
+ end
85
+ end
86
+ end
87
+
88
+ describe "Feed Engagement API" do
89
+ it "should fetch engagement for a feed" do
90
+ EM.synchrony do
91
+ eng = api.feed_engagement(IGVITA)
92
+
93
+ eng.class.should == Hash
94
+ eng.keys.size.should == 1
95
+ eng[IGVITA]['sum'].class.should == Float
96
+
97
+ EM.stop
98
+ end
99
+ end
100
+
101
+ it "should fetch daily engagement for multiple feeds" do
102
+ EM.synchrony do
103
+ eng = api.feed_engagement([IGVITA, EVERBURNING], {
104
+ :summary => false,
105
+ :start_time => 'yesterday',
106
+ :end_time => 'today'
107
+ })
108
+
109
+ eng.class.should == Hash
110
+ eng.keys.size.should == 2
111
+ eng[IGVITA].keys.size.should == 2
112
+
113
+ EM.stop
114
+ end
115
+ end
116
+ end
117
+
118
+ describe "Metrics API" do
119
+ it "should fetch metrics for a collection of urls" do
120
+ EM.synchrony do
121
+ metrics = api.metrics(['http://www.igvita.com/', 'http://www.everburning.com/'])
122
+ metrics.keys.size.should == 2
123
+
124
+ metrics['http://www.igvita.com/'].class.should == Hash
125
+ metrics['http://www.everburning.com/'].class.should == Hash
126
+
127
+ EM.stop
128
+ end
129
+ end
130
+
131
+ it "should fetch metrics via url md5s" do
132
+ EM.synchrony do
133
+ metrics = api.metrics('1c1a5357e8bd00128db845b2595d5ebe')
134
+
135
+ metrics.keys.size.should == 1
136
+ metrics['1c1a5357e8bd00128db845b2595d5ebe'].class.should == Hash
137
+
138
+ EM.stop
139
+ end
140
+ end
141
+ end
142
+
143
+ it "should invoke and kill EM reactor transparently" do
144
+ metrics = api.metrics('1c1a5357e8bd00128db845b2595d5ebe')
145
+
146
+ metrics.keys.size.should == 1
147
+ metrics['1c1a5357e8bd00128db845b2595d5ebe'].class.should == Hash
148
+ end
149
+
150
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: postrank-api
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Ilya Grigorik
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-06-03 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: eventmachine
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 12
30
+ - 9
31
+ version: 0.12.9
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: em-http
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: em-synchrony
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ type: :runtime
57
+ version_requirements: *id003
58
+ description: PostRank API Wrapper
59
+ email: ilya@igvita.com
60
+ executables: []
61
+
62
+ extensions: []
63
+
64
+ extra_rdoc_files:
65
+ - README.md
66
+ files:
67
+ - README.md
68
+ - Rakefile
69
+ - VERSION
70
+ - lib/postrank-api.rb
71
+ - lib/postrank-api/api.rb
72
+ - postrank-api.gemspec
73
+ - spec/api_spec.rb
74
+ has_rdoc: true
75
+ homepage: http://github.com/postrank/postrank-api
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options:
80
+ - --charset=UTF-8
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ segments:
88
+ - 1
89
+ - 9
90
+ - 1
91
+ version: 1.9.1
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ requirements: []
100
+
101
+ rubyforge_project: postrank-api
102
+ rubygems_version: 1.3.6
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: PostRank API Wrapper
106
+ test_files:
107
+ - spec/api_spec.rb