linked_in 0.0.21
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +27 -0
- data/PostInstall.txt +0 -0
- data/README.rdoc +104 -0
- data/Rakefile +26 -0
- data/docs/sample-oauth-plugin_token.rb +18 -0
- data/lib/linked_in.rb +27 -0
- data/lib/linked_in/base.rb +84 -0
- data/lib/linked_in/oauth.rb +65 -0
- data/lib/linked_in/request.rb +86 -0
- data/linked_in.gemspec +42 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/fixtures/connections.xml +55 -0
- data/spec/fixtures/connections_with_field_selectors.xml +11 -0
- data/spec/fixtures/empty.xml +2 -0
- data/spec/fixtures/error.xml +7 -0
- data/spec/fixtures/network.xml +99 -0
- data/spec/fixtures/people.xml +100 -0
- data/spec/fixtures/profile.xml +9 -0
- data/spec/linked_in/base_spec.rb +150 -0
- data/spec/linked_in/oauth_spec.rb +106 -0
- data/spec/linked_in/request_spec.rb +115 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +39 -0
- data/tasks/rspec.rake +21 -0
- metadata +145 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
PostInstall.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
docs/sample-oauth-plugin_token.rb
|
7
|
+
lib/linked_in.rb
|
8
|
+
lib/linked_in/base.rb
|
9
|
+
lib/linked_in/oauth.rb
|
10
|
+
lib/linked_in/request.rb
|
11
|
+
linked_in.gemspec
|
12
|
+
script/console
|
13
|
+
script/destroy
|
14
|
+
script/generate
|
15
|
+
spec/fixtures/connections.xml
|
16
|
+
spec/fixtures/connections_with_field_selectors.xml
|
17
|
+
spec/fixtures/empty.xml
|
18
|
+
spec/fixtures/error.xml
|
19
|
+
spec/fixtures/network.xml
|
20
|
+
spec/fixtures/people.xml
|
21
|
+
spec/fixtures/profile.xml
|
22
|
+
spec/linked_in/base_spec.rb
|
23
|
+
spec/linked_in/oauth_spec.rb
|
24
|
+
spec/linked_in/request_spec.rb
|
25
|
+
spec/spec.opts
|
26
|
+
spec/spec_helper.rb
|
27
|
+
tasks/rspec.rake
|
data/PostInstall.txt
ADDED
File without changes
|
data/README.rdoc
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
== DESCRIPTION:
|
2
|
+
|
3
|
+
The linked_in gem wraps the LinkedIn API making it easy to read and write profile information and messages on http://linkedin.com. Full support for OAuth is provided including a wrapper for Pelle's oauth-plugin (http://github.com/pelle/oauth-plugin/). This gem borrowed heavily from junemakers twitter gem (http://github.com/jnunemaker/twitter/).
|
4
|
+
|
5
|
+
== EXAMPLES:
|
6
|
+
|
7
|
+
First, authorize via oauth:
|
8
|
+
|
9
|
+
oauth = LinkedIn::OAuth.new('token', 'secret') # => #<LinkedIn::OAuth:0x1021db048 ...>
|
10
|
+
|
11
|
+
oauth.request_token.authorize_url # => "https://api.linked_in.com/uas/oauth/authorize?oauth_token=XXXX"
|
12
|
+
|
13
|
+
Visit authorization URL, authorize and copy PIN
|
14
|
+
|
15
|
+
oauth.authorize_from_request(oauth.request_token.token, oauth.request_token.secret, PIN)
|
16
|
+
|
17
|
+
Finally, do stuff!
|
18
|
+
|
19
|
+
linked_in = LinkedIn::Base.new(oauth)
|
20
|
+
|
21
|
+
=== Profile Information
|
22
|
+
|
23
|
+
# currently auth'd users basic profile
|
24
|
+
linked_in.profile
|
25
|
+
|
26
|
+
# currently auth'd users full profile
|
27
|
+
linked_in.profile(:my, :full)
|
28
|
+
|
29
|
+
# current auth'd users profile, with field selectors
|
30
|
+
linked_in.profile(:my, ['first-name', 'last-name'])
|
31
|
+
|
32
|
+
# get a profile by public URL
|
33
|
+
linked_in.profile(:url => 'http://www.linkedin.com/pub/peter-brown/0/116/112')
|
34
|
+
|
35
|
+
# get a profile by ID
|
36
|
+
linked_in.profile(:id => '12345')
|
37
|
+
|
38
|
+
# get full profile by ID
|
39
|
+
linked_in.profile({:id => '12123'}, :full)
|
40
|
+
|
41
|
+
# get profile by ID with field selectors
|
42
|
+
linked_in.profile({:id => '12123'}, ('first-name', 'last-name'))
|
43
|
+
|
44
|
+
=== Connection Information
|
45
|
+
|
46
|
+
# currently auth'd users connections
|
47
|
+
linked_in.connections
|
48
|
+
|
49
|
+
=== Network Updates
|
50
|
+
|
51
|
+
# currently auth'd users network updates
|
52
|
+
linked_in.network
|
53
|
+
|
54
|
+
# limit updates
|
55
|
+
linked_in.network(:start => 0, :count => 20)
|
56
|
+
|
57
|
+
== REQUIREMENTS:
|
58
|
+
|
59
|
+
A few gems are required:
|
60
|
+
|
61
|
+
* forwardable
|
62
|
+
* oauth
|
63
|
+
* xml-object
|
64
|
+
|
65
|
+
You'll also need a LinkedIn API account, which you can get here:
|
66
|
+
|
67
|
+
* http://developer.linked_in.com/community/apis
|
68
|
+
|
69
|
+
== INSTALL:
|
70
|
+
|
71
|
+
sudo gem install gemcutter
|
72
|
+
gem tumble
|
73
|
+
sudo gem install linked_in
|
74
|
+
|
75
|
+
== TODO:
|
76
|
+
|
77
|
+
* add support for messaging between connections (http://developer.linked_in.com/docs/DOC-1044)
|
78
|
+
* add support for get network updates (http://developer.linked_in.com/docs/DOC-1006)
|
79
|
+
* add support for search API (http://developer.linked_in.com/docs/DOC-1005)
|
80
|
+
|
81
|
+
== LICENSE:
|
82
|
+
|
83
|
+
(The MIT License)
|
84
|
+
|
85
|
+
Copyright (c) 2009 FIXME full name
|
86
|
+
|
87
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
88
|
+
a copy of this software and associated documentation files (the
|
89
|
+
'Software'), to deal in the Software without restriction, including
|
90
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
91
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
92
|
+
permit persons to whom the Software is furnished to do so, subject to
|
93
|
+
the following conditions:
|
94
|
+
|
95
|
+
The above copyright notice and this permission notice shall be
|
96
|
+
included in all copies or substantial portions of the Software.
|
97
|
+
|
98
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
99
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
100
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
101
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
102
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
103
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
104
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe' #, '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/linked_in'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
# Hoe.plugin :website
|
9
|
+
# Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
# Generate all the Rake tasks
|
12
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
13
|
+
$hoe = Hoe.spec 'linked_in' do
|
14
|
+
self.developer 'Peter T. Brown', 'peter@pathable.com'
|
15
|
+
self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
|
16
|
+
self.rubyforge_name = self.name # TODO this is default value
|
17
|
+
# self.extra_deps = [['activesupport','>= 2.0.2']]
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'newgem/tasks'
|
22
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
23
|
+
|
24
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
25
|
+
# remove_task :default
|
26
|
+
# task :default => [:spec, :features]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'linked_in'
|
2
|
+
require 'oauth/models/consumers/token'
|
3
|
+
|
4
|
+
class ConsumerToken < ActiveRecord::Base
|
5
|
+
include Oauth::Models::Consumers::Token
|
6
|
+
end
|
7
|
+
|
8
|
+
class LinkedInToken < ConsumerToken
|
9
|
+
def client
|
10
|
+
unless @client
|
11
|
+
@linkedin_oauth = LinkedIn::OAuth.new(LinkedInToken.consumer.key, LinkedInToken.consumer.secret)
|
12
|
+
@linkedin_oauth.authorize_from_access(token,secret)
|
13
|
+
@client = LinkedIn::Base.new(@linkedin_oauth)
|
14
|
+
end
|
15
|
+
|
16
|
+
@client
|
17
|
+
end
|
18
|
+
end
|
data/lib/linked_in.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'forwardable'
|
6
|
+
gem 'oauth', '>= 0.3.5'
|
7
|
+
require 'oauth'
|
8
|
+
gem 'jordi-xml-object', '>= 0.9.91'
|
9
|
+
require 'xml-object'
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
module LinkedIn
|
14
|
+
VERSION = '0.0.21'
|
15
|
+
|
16
|
+
class LinkedInError < StandardError; end
|
17
|
+
class Unauthorized < LinkedInError; end
|
18
|
+
class General < LinkedInError; end
|
19
|
+
class Unavailable < StandardError; end
|
20
|
+
class NotFound < StandardError; end
|
21
|
+
class Forbidden < StandardError; end
|
22
|
+
|
23
|
+
autoload :Base, File.join(File.dirname(__FILE__), *%w[linked_in base.rb])
|
24
|
+
autoload :OAuth, File.join(File.dirname(__FILE__), *%w[linked_in oauth.rb])
|
25
|
+
autoload :Request, File.join(File.dirname(__FILE__), *%w[linked_in request.rb])
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Base
|
3
|
+
API_VERSION = 'v1'
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators :client, :get, :post, :put
|
7
|
+
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
def initialize(client)
|
11
|
+
@client = client
|
12
|
+
end
|
13
|
+
|
14
|
+
# Lookup Options: url=<public profile URL>, id=<member id>
|
15
|
+
# Field Options: :full
|
16
|
+
def profile(lookup = nil, fields = nil, query = {})
|
17
|
+
path = profile_path(nil, lookup, fields)
|
18
|
+
perform_get(path, :query => query)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Query options: start=N, count=N
|
22
|
+
def people(query = {})
|
23
|
+
path = api_path('people')
|
24
|
+
perform_get(path, query)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Query options: start=N, count=N
|
28
|
+
def connections(lookup = nil, fields = nil, query = {})
|
29
|
+
path = profile_path('connections', lookup, fields)
|
30
|
+
perform_get(path, :query => query)
|
31
|
+
end
|
32
|
+
|
33
|
+
def network(query = {})
|
34
|
+
path = profile_path('network')
|
35
|
+
perform_get(path, :query => query)
|
36
|
+
end
|
37
|
+
|
38
|
+
def current_status(update)
|
39
|
+
path = profile_path('current-status')
|
40
|
+
update_xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><current-status>#{update}</current-status>"
|
41
|
+
perform_put(path, :body => update_xml)
|
42
|
+
end
|
43
|
+
|
44
|
+
# update is something like: '<a href="http://www.linkedin.com/profile?viewProfile=&key=ABCDEFG">Richard Brautigan</a> is reading about<a href="http://www.tigers.com">Tigers</a>http://www.tigers.com>Tigers</a>.'}
|
45
|
+
def person_activities(update, timestamp = nil)
|
46
|
+
path = profile_path('person-activities')
|
47
|
+
body = {'activity' => {
|
48
|
+
'timestamp' => (timestamp || Time.now.to_i),
|
49
|
+
'content-type' => 'linkedin-html',
|
50
|
+
'body' => update}
|
51
|
+
}
|
52
|
+
perform_post(path, body)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def api_path(p)
|
59
|
+
"/#{API_VERSION}/#{p}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Generate LinkedIn API compatable paths
|
63
|
+
def profile_path(api = nil, lookup = nil, fields = nil)
|
64
|
+
path = api_path('people') + '/'
|
65
|
+
path += lookup.kind_of?(Hash) ? "#{lookup.first[0]}=#{lookup.first[1]}" : ([nil, :my].include?(lookup) ? '~' : lookup.to_s)
|
66
|
+
path += api.nil? ? '' : "/#{api}"
|
67
|
+
path += fields.kind_of?(Array) ? ":(#{fields.collect{|f| f.to_s}.join(',')})" : (fields.nil? ? '' : ":#{fields.to_s}")
|
68
|
+
path
|
69
|
+
end
|
70
|
+
|
71
|
+
def perform_get(path, options={})
|
72
|
+
LinkedIn::Request.get(self, path, options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def perform_post(path, options={})
|
76
|
+
LinkedIn::Request.post(self, path, options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def perform_put(path, options={})
|
80
|
+
LinkedIn::Request.put(self, path, options)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
# >> oauth = LinkedIn::OAuth.new('token', 'secret')
|
3
|
+
# => #<LinkedIn::OAuth:0x1021db048 ...>
|
4
|
+
# >> oauth.request_token.authorize_url
|
5
|
+
# => "https://api.linkedin.com/uas/oauth/authorize?oauth_token=XXXX" # goto URL
|
6
|
+
# >> oauth.authorize_from_request(oauth.request_token.token, oauth.request_token.secret, PIN)
|
7
|
+
# DONE
|
8
|
+
class OAuth
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
SITE_URL = 'https://api.linkedin.com'
|
12
|
+
|
13
|
+
def_delegators :access_token, :get, :post, :put
|
14
|
+
|
15
|
+
attr_reader :ctoken, :csecret, :consumer_options
|
16
|
+
|
17
|
+
# Options
|
18
|
+
def initialize(ctoken, csecret, options={})
|
19
|
+
@ctoken = ctoken
|
20
|
+
@csecret = csecret
|
21
|
+
@consumer_options = {
|
22
|
+
:request_token_path => "/uas/oauth/requestToken?oauth_callback=oob",
|
23
|
+
:access_token_path => "/uas/oauth/accessToken",
|
24
|
+
:authorize_path => "/uas/oauth/authorize"
|
25
|
+
}.merge(options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def consumer
|
29
|
+
@consumer ||= ::OAuth::Consumer.new(@ctoken, @csecret, {:site => SITE_URL}.merge(consumer_options))
|
30
|
+
end
|
31
|
+
|
32
|
+
def set_callback_url(url)
|
33
|
+
clear_request_token
|
34
|
+
request_token(:oauth_callback => url)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Note: If using oauth with a web app, be sure to provide :oauth_callback.
|
38
|
+
# Options:
|
39
|
+
# :oauth_callback => String, url that linkedin should redirect to
|
40
|
+
def request_token(options={})
|
41
|
+
@request_token ||= consumer.get_request_token(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
# For web apps use params[:oauth_verifier], for desktop apps,
|
45
|
+
# use the verifier is the pin that twitter gives users.
|
46
|
+
def authorize_from_request(rtoken, rsecret, verifier_or_pin)
|
47
|
+
request_token = ::OAuth::RequestToken.new(consumer, rtoken, rsecret)
|
48
|
+
access_token = request_token.get_access_token(:oauth_verifier => verifier_or_pin)
|
49
|
+
@atoken, @asecret = access_token.token, access_token.secret
|
50
|
+
end
|
51
|
+
|
52
|
+
def access_token
|
53
|
+
@access_token ||= ::OAuth::AccessToken.new(consumer, @atoken, @asecret)
|
54
|
+
end
|
55
|
+
|
56
|
+
def authorize_from_access(atoken, asecret)
|
57
|
+
@atoken, @asecret = atoken, asecret
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def clear_request_token
|
62
|
+
@request_token = nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Request
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def self.get(client, path, options = {})
|
6
|
+
new(client, :get, path, options).perform
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.post(client, path, options = {})
|
10
|
+
new(client, :post, path, options).perform
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.put(client, path, options = {})
|
14
|
+
new(client, :put, path, options).perform
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :client, :method, :path, :options
|
18
|
+
|
19
|
+
def_delegators :client, :get, :post, :put
|
20
|
+
|
21
|
+
def initialize(client, method, path, options={})
|
22
|
+
@client, @method, @path, @options = client, method, path, {:mash => true}.merge(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def uri
|
26
|
+
@uri ||= begin
|
27
|
+
uri = URI.parse(path)
|
28
|
+
|
29
|
+
if options[:query] && options[:query] != {}
|
30
|
+
uri.query = to_query(options[:query])
|
31
|
+
end
|
32
|
+
|
33
|
+
uri.to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def perform
|
38
|
+
make_friendly(send("perform_#{method}"))
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def perform_get
|
43
|
+
send(:get, uri, options[:headers])
|
44
|
+
end
|
45
|
+
|
46
|
+
def perform_post
|
47
|
+
send(:post, uri, options[:body], options[:headers])
|
48
|
+
end
|
49
|
+
|
50
|
+
def perform_put
|
51
|
+
send(:put, uri, options[:body], options[:headers])
|
52
|
+
end
|
53
|
+
|
54
|
+
def make_friendly(response)
|
55
|
+
raise_errors(response)
|
56
|
+
parse(response)
|
57
|
+
end
|
58
|
+
|
59
|
+
def raise_errors(response)
|
60
|
+
case response.code.to_i
|
61
|
+
when 401 # NotAuthorized
|
62
|
+
raise Unauthorized.new, "#{response.code}: #{response.message}"
|
63
|
+
when 403 # Forbidden
|
64
|
+
raise Forbidden.new, "#{response.code}: #{response.message}"
|
65
|
+
when 404 # NotFound
|
66
|
+
raise NotFound, "#{response.code}: #{response.message}"
|
67
|
+
when 500 # InternalServerError
|
68
|
+
raise Unavailable, "LinkedIn had an internal error #{response.code}: #{response.message}"
|
69
|
+
when 502..503 # Unavailable, BadGateway
|
70
|
+
raise Unavailable, "#{response.code}: #{response.message}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse(response)
|
75
|
+
return if response.nil? || response.body.blank?
|
76
|
+
XMLObject.new(response.body)
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_query(options)
|
80
|
+
options.inject([]) do |collection, opt|
|
81
|
+
collection << "#{opt[0]}=#{opt[1]}"
|
82
|
+
collection
|
83
|
+
end * '&'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|