cl_linkedin 0.2.3
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/.autotest +14 -0
- data/.document +5 -0
- data/.gitignore +25 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.markdown +80 -0
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/changelog.markdown +76 -0
- data/examples/authenticate.rb +21 -0
- data/examples/network.rb +12 -0
- data/examples/profile.rb +12 -0
- data/examples/status.rb +9 -0
- data/lib/linked_in/api_standard_profile_request.rb +17 -0
- data/lib/linked_in/authorization_helpers.rb +48 -0
- data/lib/linked_in/base.rb +13 -0
- data/lib/linked_in/birthdate.rb +21 -0
- data/lib/linked_in/client.rb +154 -0
- data/lib/linked_in/company.rb +11 -0
- data/lib/linked_in/connections.rb +16 -0
- data/lib/linked_in/country.rb +9 -0
- data/lib/linked_in/current_share.rb +56 -0
- data/lib/linked_in/education.rb +41 -0
- data/lib/linked_in/error.rb +21 -0
- data/lib/linked_in/group.rb +32 -0
- data/lib/linked_in/im_account.rb +28 -0
- data/lib/linked_in/languages.rb +28 -0
- data/lib/linked_in/likes.rb +23 -0
- data/lib/linked_in/location.rb +13 -0
- data/lib/linked_in/message.rb +20 -0
- data/lib/linked_in/network.rb +12 -0
- data/lib/linked_in/patents.rb +42 -0
- data/lib/linked_in/people.rb +18 -0
- data/lib/linked_in/person.rb +7 -0
- data/lib/linked_in/phone_number.rb +29 -0
- data/lib/linked_in/position.rb +46 -0
- data/lib/linked_in/profile.rb +94 -0
- data/lib/linked_in/publications.rb +40 -0
- data/lib/linked_in/recipient.rb +7 -0
- data/lib/linked_in/recipients.rb +18 -0
- data/lib/linked_in/recommendations.rb +30 -0
- data/lib/linked_in/request_helpers.rb +78 -0
- data/lib/linked_in/short_profile.rb +13 -0
- data/lib/linked_in/skill.rb +33 -0
- data/lib/linked_in/to_xml_helpers.rb +53 -0
- data/lib/linked_in/twitter_account.rb +28 -0
- data/lib/linked_in/update.rb +23 -0
- data/lib/linked_in/url_resource.rb +26 -0
- data/lib/linkedin.rb +83 -0
- data/linkedin.gemspec +49 -0
- data/spec/cases/client_spec.rb +230 -0
- data/spec/cases/linkedin_spec.rb +37 -0
- data/spec/cases/oauth_spec.rb +109 -0
- data/spec/client_shared_examples.orig.rb +91 -0
- data/spec/client_shared_examples.rb +104 -0
- data/spec/fixtures/403.xml +7 -0
- data/spec/fixtures/404.xml +7 -0
- data/spec/fixtures/blank.xml +0 -0
- data/spec/fixtures/connections.xml +3733 -0
- data/spec/fixtures/likes.xml +18 -0
- data/spec/fixtures/mailbox_items.xml +16 -0
- data/spec/fixtures/network_status_with_group.xml +44 -0
- data/spec/fixtures/network_statuses.xml +317 -0
- data/spec/fixtures/picture_updates.xml +117 -0
- data/spec/fixtures/profile.xml +9 -0
- data/spec/fixtures/profile_full.orig.xml +3909 -0
- data/spec/fixtures/profile_full.xml +395 -0
- data/spec/fixtures/profile_with_positions.xml +79 -0
- data/spec/fixtures/search.xml +538 -0
- data/spec/fixtures/shares.xml +12 -0
- data/spec/fixtures/status.xml +2 -0
- data/spec/spec_helper.rb +58 -0
- metadata +242 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Profile < LinkedIn::Base
|
3
|
+
|
4
|
+
# xml_reader :three_current_positions, :as => [Position]
|
5
|
+
|
6
|
+
PROFILE_FIELDS = %w[id first_name last_name headline industry
|
7
|
+
current_status current_status_timestamp summary
|
8
|
+
specialties proposal_comments associations honors
|
9
|
+
interests picture_url distance num_recommenders public_profile_url twitter_accounts im_accounts]
|
10
|
+
|
11
|
+
PROFILE_FIELDS.each do |f|
|
12
|
+
define_method(f.to_sym) do
|
13
|
+
@doc.xpath("./person/#{f.gsub(/_/,'-')}").text
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def birthdate
|
18
|
+
@birthday ||= Birthdate.new(@doc.xpath('./person/date-of-birth'))
|
19
|
+
end
|
20
|
+
|
21
|
+
def location
|
22
|
+
@location ||= Location.new(@doc)
|
23
|
+
end
|
24
|
+
|
25
|
+
def api_standard_profile_request
|
26
|
+
@api_standard ||= ApiStandardProfileRequest.new(@doc.xpath('./person/api-standard-profile-request'))
|
27
|
+
end
|
28
|
+
|
29
|
+
def site_standard_profile_request
|
30
|
+
@doc.xpath('/person/site-standard-profile-request/url').text
|
31
|
+
end
|
32
|
+
|
33
|
+
def relation_to_viewer
|
34
|
+
@doc.xpath('//relation-to-viewer/distance').text
|
35
|
+
end
|
36
|
+
|
37
|
+
def member_url_resources
|
38
|
+
@url_resources ||= UrlResource.new(@doc.xpath('//member-url-resources')).resources
|
39
|
+
end
|
40
|
+
|
41
|
+
def positions
|
42
|
+
@positions ||= Position.new(@doc.xpath('//positions')).positions
|
43
|
+
end
|
44
|
+
|
45
|
+
def educations
|
46
|
+
@educations ||= Education.new(@doc.xpath('//educations')).educations
|
47
|
+
end
|
48
|
+
|
49
|
+
def connections
|
50
|
+
@connections ||= Connections.new(@doc.xpath('//connections')).connections
|
51
|
+
end
|
52
|
+
|
53
|
+
def groups
|
54
|
+
@groups ||= Group.new(@doc.xpath('//member-groups')).groups
|
55
|
+
end
|
56
|
+
|
57
|
+
def recommendations
|
58
|
+
@recommendations ||= Recommendations.new(@doc.xpath('//recommendations-received')).recommendations
|
59
|
+
end
|
60
|
+
|
61
|
+
def current_share
|
62
|
+
@current_share ||= CurrentShare.new(@doc.xpath('//current-share'))
|
63
|
+
end
|
64
|
+
|
65
|
+
def languages
|
66
|
+
@languages ||= Languages.new(@doc.xpath('//languages')).languages
|
67
|
+
end
|
68
|
+
|
69
|
+
def skills
|
70
|
+
@skills ||= Skill.new(@doc.xpath('//skills')).skills
|
71
|
+
end
|
72
|
+
|
73
|
+
def phone_numbers
|
74
|
+
@phone_number ||= PhoneNumber.new(@doc.xpath('//phone-numbers')).phone_numbers
|
75
|
+
end
|
76
|
+
|
77
|
+
def publications
|
78
|
+
@publications ||= Publications.new(@doc.xpath('//publications')).publications
|
79
|
+
end
|
80
|
+
|
81
|
+
def patents
|
82
|
+
@patents ||= Patents.new(@doc.xpath('//patents')).patents
|
83
|
+
end
|
84
|
+
|
85
|
+
def twitter_accounts
|
86
|
+
@twitter_accounts ||= TwitterAccount.new(@doc.xpath('//twitter-accounts')).twitter_accounts
|
87
|
+
end
|
88
|
+
|
89
|
+
def im_accounts
|
90
|
+
@im_accounts ||= ImAccount.new(@doc.xpath('//im-accounts')).im_accounts
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Publications < LinkedIn::Base
|
3
|
+
|
4
|
+
def publications
|
5
|
+
@publications ||= @doc.children.inject([]) do |list, publication|
|
6
|
+
list << Resource.new(publication) unless publication.blank?
|
7
|
+
list
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Resource
|
12
|
+
def initialize(publication)
|
13
|
+
@publication = publication
|
14
|
+
end
|
15
|
+
|
16
|
+
%w[id title].each do |f|
|
17
|
+
define_method(f.to_sym) do
|
18
|
+
@publication.xpath("./#{f.gsub(/_/,'-')}").text
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def year
|
23
|
+
@year ||= @publication.xpath("./date/year").text.to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
def day
|
27
|
+
@day ||= @publication.xpath("./date/day").text.to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
def month
|
31
|
+
@month ||= @publication.xpath("./date/month").text.to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
def date
|
35
|
+
Date.civil(year, month, day)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Recipients
|
3
|
+
|
4
|
+
attr_accessor :recipients
|
5
|
+
|
6
|
+
def to_xml
|
7
|
+
self.to_xml_nodes(Nokogiri.XML('<root/>', nil, 'UTF-8')).to_xml
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_xml_nodes(doc)
|
11
|
+
recipients.inject(Nokogiri::XML::NodeSet.new(doc)) do |nodes, recipient|
|
12
|
+
node = Nokogiri::XML::DocumentFragment.new(doc, '<recipient><person/></recipient>')
|
13
|
+
node.at_css('person')['path'] = recipient.person.path
|
14
|
+
nodes << node
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
|
3
|
+
class Recommendations < LinkedIn::Base
|
4
|
+
|
5
|
+
def recommendations
|
6
|
+
@recommendations ||= @doc.children.inject([]) do |list, recommendation|
|
7
|
+
list << Recommendation.new(recommendation) unless recommendation.blank?
|
8
|
+
list
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Recommendation
|
13
|
+
def initialize(recommendation)
|
14
|
+
@recommendation = recommendation
|
15
|
+
end
|
16
|
+
|
17
|
+
%w[id recommendation_type recommendation_text].each do |f|
|
18
|
+
define_method(f.to_sym) do
|
19
|
+
@recommendation.xpath("./#{f.gsub(/_/,'-')}").text
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def recommender
|
24
|
+
@recommender ||= ShortProfile.new(@recommendation.xpath('./recommender'))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
|
3
|
+
module RequestHelpers
|
4
|
+
|
5
|
+
def get(path, options={})
|
6
|
+
path = "/v1#{path}"
|
7
|
+
response = access_token.get(path, options)
|
8
|
+
raise_errors(response)
|
9
|
+
response.body
|
10
|
+
end
|
11
|
+
|
12
|
+
def post(path, body='', options={})
|
13
|
+
path = "/v1#{path}"
|
14
|
+
default_options = { 'Content-Type' => 'application/xml' }
|
15
|
+
response = access_token.post(path, body, default_options.merge(options))
|
16
|
+
raise_errors(response)
|
17
|
+
response
|
18
|
+
end
|
19
|
+
|
20
|
+
def put(path, body, options={})
|
21
|
+
path = "/v1#{path}"
|
22
|
+
response = access_token.put(path, body, options)
|
23
|
+
raise_errors(response)
|
24
|
+
response
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete(path, options={})
|
28
|
+
path = "/v1#{path}"
|
29
|
+
response = access_token.delete(path, options)
|
30
|
+
raise_errors(response)
|
31
|
+
response
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def raise_errors(response)
|
37
|
+
# Even if the XML answer contains the HTTP status code, LinkedIn also sets this code
|
38
|
+
# in the HTTP answer (thankfully).
|
39
|
+
if response.code.to_i >= 400
|
40
|
+
data = LinkedIn::Error.from_xml(response.body)
|
41
|
+
message = "(#{response.code}): #{data.message} - #{data.code}"
|
42
|
+
|
43
|
+
case response.code.to_i
|
44
|
+
when 400
|
45
|
+
raise BadRequest.new(data), message
|
46
|
+
when 401
|
47
|
+
raise Unauthorized.new(data), message
|
48
|
+
when 403
|
49
|
+
raise Forbidden.new(data), message
|
50
|
+
when 404
|
51
|
+
raise NotFound.new(data), message
|
52
|
+
when 500
|
53
|
+
raise InformLinkedIn, "LinkedIn had an internal error. Please let them know in the forum. #{message}"
|
54
|
+
when 502..503
|
55
|
+
raise Unavailable, message
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_query(options)
|
61
|
+
query_string = options.inject([]) do |collection, opt|
|
62
|
+
collection << "#{opt[0]}=#{opt[1]}"
|
63
|
+
collection
|
64
|
+
end * '&'
|
65
|
+
URI.escape(query_string)
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_uri(path, options)
|
69
|
+
uri = URI.parse(path)
|
70
|
+
|
71
|
+
if options && options != {}
|
72
|
+
uri.query = to_query(options)
|
73
|
+
end
|
74
|
+
uri.to_s
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Skill < LinkedIn::Base
|
3
|
+
|
4
|
+
def skills
|
5
|
+
@array ||= begin
|
6
|
+
@array = []
|
7
|
+
@doc.children.each do |skill|
|
8
|
+
@array << Resource.new(skill) unless skill.blank?
|
9
|
+
end
|
10
|
+
@array
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Resource
|
15
|
+
|
16
|
+
def initialize(skill)
|
17
|
+
@skill = skill
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
@skill.xpath("./skill/name").text
|
22
|
+
end
|
23
|
+
|
24
|
+
%w[id].each do |f| #proficiency level to come? http://developer.linkedin.com/community/apis/blog/2011/01/04/new-profile-fields-are-here
|
25
|
+
define_method(f.to_sym) do
|
26
|
+
@skill.xpath("./#{f.gsub(/_/,'-')}").text
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end # resource class
|
31
|
+
|
32
|
+
end # skill class
|
33
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
module ToXmlHelpers
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def status_to_xml(status)
|
7
|
+
doc = Nokogiri.XML('<current-status/>', nil, 'UTF-8')
|
8
|
+
doc.root.content = status
|
9
|
+
doc.to_xml
|
10
|
+
end
|
11
|
+
|
12
|
+
def message_to_xml(message)
|
13
|
+
doc = Nokogiri.XML('')
|
14
|
+
doc.encoding = 'UTF-8'
|
15
|
+
doc.root = message.to_xml_node(doc)
|
16
|
+
doc.to_xml
|
17
|
+
end
|
18
|
+
|
19
|
+
def share_to_xml(options={})
|
20
|
+
doc = Nokogiri.XML('<share><comment/><content><title/><submitted-url/><submitted-image-url/></content><visibility><code/></visibility></share>')
|
21
|
+
doc.encoding = 'UTF-8'
|
22
|
+
|
23
|
+
{:comment => 'comment', :title => 'title', :url => 'submitted-url', :image_url => 'submitted-image-url'}.each do |key, name|
|
24
|
+
doc.at_css(name).content = options[key] if options[key]
|
25
|
+
end
|
26
|
+
|
27
|
+
doc.at_css('visibility > code').content = options[:visibility] || options[:visability] # backward-compatible typo fix
|
28
|
+
doc.to_xml
|
29
|
+
end
|
30
|
+
|
31
|
+
def comment_to_xml(comment)
|
32
|
+
doc = Nokogiri.XML('<update-comment><comment/><update-comment/>')
|
33
|
+
doc.encoding = 'UTF-8'
|
34
|
+
doc.at_css('comment').content = comment
|
35
|
+
doc.to_xml
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_liked_to_xml(is_liked)
|
39
|
+
doc = Nokogiri.XML('<is-liked/>')
|
40
|
+
doc.encoding = 'UTF-8'
|
41
|
+
doc.at_css('is-liked').content = is_liked
|
42
|
+
doc.to_xml
|
43
|
+
end
|
44
|
+
|
45
|
+
def network_update_to_xml(message)
|
46
|
+
doc = Nokogiri::XML('<activity locale="en_US"><content-type>linkedin-html</content-type><body/></activity>')
|
47
|
+
doc.encoding = 'UTF-8'
|
48
|
+
doc.at_css('body').content = message
|
49
|
+
doc.to_xml
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class TwitterAccount < LinkedIn::Base
|
3
|
+
|
4
|
+
def twitter_accounts
|
5
|
+
@twitter_accounts ||= @doc.children.inject([]) do |list, twitter_account|
|
6
|
+
list << Resource.new(twitter_account) unless twitter_account.blank?
|
7
|
+
list
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Resource
|
12
|
+
|
13
|
+
def initialize(twitter_account)
|
14
|
+
@twitter_account = twitter_account
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@twitter_account.xpath("./provider-account-name").text
|
19
|
+
end
|
20
|
+
|
21
|
+
def id
|
22
|
+
@twitter_account.xpath("./provider-account-id").text
|
23
|
+
end
|
24
|
+
|
25
|
+
end # resource class
|
26
|
+
|
27
|
+
end # skill class
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Update < LinkedIn::Base
|
3
|
+
|
4
|
+
%w[update_key update_type is_commentable?].each do |f|
|
5
|
+
define_method(f.to_sym) do
|
6
|
+
@doc.xpath("./update/#{f.gsub(/_/,'-').gsub(/\?$/,"")}").text
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def timestamp
|
11
|
+
@doc.xpath('./update/timestamp').text.to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
def profile
|
15
|
+
Profile.new(Nokogiri::XML(@doc.xpath('./update/update-content/person').to_xml))
|
16
|
+
end
|
17
|
+
|
18
|
+
def likes
|
19
|
+
Likes.new(Nokogiri::XML(@doc.xpath('./update/likes').to_xml)).likes
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class UrlResource < LinkedIn::Base
|
3
|
+
|
4
|
+
def resources
|
5
|
+
@resources ||= @doc.children.inject([]) do |list, url|
|
6
|
+
list << Resource.new(url) unless url.blank?
|
7
|
+
list
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Resource
|
12
|
+
|
13
|
+
def initialize(member_url)
|
14
|
+
@member_url = member_url
|
15
|
+
end
|
16
|
+
|
17
|
+
%w[url name].each do |f|
|
18
|
+
define_method(f.to_sym) do
|
19
|
+
@member_url.xpath("./#{f.gsub(/_/,'-')}").text
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end # resource class
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|