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 ADDED
@@ -0,0 +1,4 @@
1
+ === 0.0.1 2009-10-25
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
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: '&lt;a href=&quot;http://www.linkedin.com/profile?viewProfile=&amp;key=ABCDEFG&quot;&gt;Richard Brautigan&lt;/a&gt; is reading about&lt;a href=&quot;http://www.tigers.com&quot;&gt;Tigers&lt;/a&gt;http://www.tigers.com&gt;Tigers&lt;/a&gt;.'}
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