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.
- data/README.md +29 -0
- data/Rakefile +22 -0
- data/VERSION +1 -0
- data/lib/postrank-api.rb +1 -0
- data/lib/postrank-api/api.rb +140 -0
- data/postrank-api.gemspec +57 -0
- data/spec/api_spec.rb +150 -0
- metadata +107 -0
data/README.md
ADDED
@@ -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/')
|
data/Rakefile
ADDED
@@ -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
|
data/lib/postrank-api.rb
ADDED
@@ -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
|
+
|
data/spec/api_spec.rb
ADDED
@@ -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
|