linkedin 0.1.7 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +14 -0
- data/.document +5 -0
- data/.gitignore +25 -0
- data/Gemfile +3 -0
- data/README.markdown +12 -7
- data/Rakefile +10 -33
- data/VERSION +1 -1
- data/changelog.markdown +38 -0
- data/examples/authenticate.rb +1 -1
- data/lib/linked_in/api_standard_profile_request.rb +14 -6
- 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 +86 -146
- data/lib/linked_in/company.rb +9 -7
- data/lib/linked_in/connections.rb +14 -5
- data/lib/linked_in/country.rb +7 -5
- data/lib/linked_in/current_share.rb +56 -0
- data/lib/linked_in/education.rb +40 -14
- data/lib/linked_in/error.rb +19 -8
- data/lib/linked_in/group.rb +30 -7
- data/lib/linked_in/languages.rb +28 -0
- data/lib/linked_in/likes.rb +23 -0
- data/lib/linked_in/location.rb +11 -6
- data/lib/linked_in/message.rb +20 -0
- data/lib/linked_in/network.rb +10 -5
- data/lib/linked_in/patents.rb +42 -0
- data/lib/linked_in/people.rb +16 -8
- data/lib/linked_in/person.rb +7 -0
- data/lib/linked_in/phone_number.rb +29 -0
- data/lib/linked_in/position.rb +45 -14
- data/lib/linked_in/profile.rb +81 -28
- 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 +76 -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/update.rb +21 -9
- data/lib/linked_in/url_resource.rb +24 -6
- data/lib/linkedin.rb +58 -59
- data/linkedin.gemspec +52 -0
- data/spec/cases/client_spec.rb +281 -0
- data/spec/cases/linkedin_spec.rb +37 -0
- data/spec/cases/oauth_spec.rb +109 -0
- data/{test → spec}/fixtures/blank.xml +0 -0
- data/{test → spec}/fixtures/connections.xml +0 -0
- data/{test → spec}/fixtures/error.xml +0 -0
- data/spec/fixtures/likes.xml +18 -0
- data/spec/fixtures/mailbox_items.xml +16 -0
- data/{test → spec}/fixtures/network_status_with_group.xml +3 -3
- data/{test → spec}/fixtures/network_statuses.xml +18 -0
- data/{test → spec}/fixtures/picture_updates.xml +0 -0
- data/{test → spec}/fixtures/profile.xml +0 -0
- data/{test → spec}/fixtures/profile_full.xml +57 -0
- data/{test → spec}/fixtures/profile_with_positions.xml +0 -0
- data/{test → spec}/fixtures/search.xml +2 -2
- data/spec/fixtures/shares.xml +12 -0
- data/{test → spec}/fixtures/status.xml +0 -0
- data/spec/spec_helper.rb +49 -0
- metadata +160 -76
- data/test/client_test.rb +0 -124
- data/test/oauth_test.rb +0 -117
- data/test/test_helper.rb +0 -51
data/lib/linked_in/position.rb
CHANGED
@@ -1,15 +1,46 @@
|
|
1
1
|
module LinkedIn
|
2
|
-
class Position
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
2
|
+
class Position < LinkedIn::Base
|
3
|
+
|
4
|
+
def positions
|
5
|
+
@positions ||= @doc.children.inject([]) do |list, position|
|
6
|
+
list << Resource.new(position) unless position.blank?
|
7
|
+
list
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Resource
|
12
|
+
|
13
|
+
def initialize(position)
|
14
|
+
@position = position
|
15
|
+
end
|
16
|
+
|
17
|
+
%w[id title summary is_current].each do |f|
|
18
|
+
define_method(f.to_sym) do
|
19
|
+
@position.xpath("./#{f.gsub(/_/,'-')}").text
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def start_month
|
24
|
+
@position.xpath('./start-date/month').text.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def start_year
|
28
|
+
@position.xpath('./start-date/year').text.to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
def end_month
|
32
|
+
@position.xpath('./end-date/month').text.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
def end_year
|
36
|
+
@position.xpath('./end-date/year').text.to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
def company
|
40
|
+
@company ||= Company.new(@position.xpath('./company'))
|
41
|
+
end
|
42
|
+
|
43
|
+
end # resource
|
44
|
+
|
45
|
+
end # class
|
46
|
+
end # module
|
data/lib/linked_in/profile.rb
CHANGED
@@ -1,32 +1,85 @@
|
|
1
1
|
module LinkedIn
|
2
|
-
class Profile
|
3
|
-
include ROXML
|
4
|
-
xml_convention {|val| val.gsub("_","-") }
|
5
|
-
xml_reader :id
|
6
|
-
xml_reader :first_name
|
7
|
-
xml_reader :last_name
|
8
|
-
xml_reader :headline
|
9
|
-
xml_reader :location, :as => Location
|
10
|
-
xml_reader :industry
|
11
|
-
xml_reader :distance, :as => Integer
|
12
|
-
xml_reader :relation_to_viewer, :as => {:key => :name, :value => :content}
|
13
|
-
xml_reader :num_recommenders, :as => Integer
|
14
|
-
xml_reader :current_status
|
15
|
-
xml_reader :current_status_timestamp
|
16
|
-
xml_reader :connections, :as => [Profile], :from => "connections/person"
|
17
|
-
xml_reader :summary
|
18
|
-
xml_reader :specialties
|
19
|
-
xml_reader :proposal_comments
|
20
|
-
xml_reader :associations
|
21
|
-
xml_reader :honors
|
22
|
-
xml_reader :interests
|
23
|
-
xml_reader :positions, :as => [Position]
|
24
|
-
xml_reader :education, :as => [Education]
|
25
|
-
xml_reader :three_current_positions, :as => [Position]
|
26
|
-
xml_reader :member_url_resources, :as => [UrlResource], :from => 'member-url-resources/member-url'
|
27
|
-
xml_reader :api_standard_profile_request
|
28
|
-
xml_reader :site_standard_profile_request, :as => ApiStandardProfileRequest
|
29
|
-
xml_reader :picture_url
|
2
|
+
class Profile < LinkedIn::Base
|
30
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]
|
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('//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
|
31
84
|
end
|
32
85
|
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,76 @@
|
|
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
|
+
case response.code.to_i
|
40
|
+
when 400
|
41
|
+
data = LinkedIn::Error.from_xml(response.body)
|
42
|
+
raise RateLimitExceeded.new(data), "(#{response.code}): #{response.message} - #{data.code if data}"
|
43
|
+
when 401
|
44
|
+
data = LinkedIn::Error.from_xml(response.body)
|
45
|
+
raise Unauthorized.new(data), "(#{response.code}): #{response.message} - #{data.code if data}"
|
46
|
+
when 403
|
47
|
+
data = LinkedIn::Error.from_xml(response.body)
|
48
|
+
raise General.new(data), "(#{response.code}): #{response.message} - #{data.code if data}"
|
49
|
+
when 404
|
50
|
+
raise NotFound, "(#{response.code}): #{response.message}"
|
51
|
+
when 500
|
52
|
+
raise InformLinkedIn, "LinkedIn had an internal error. Please let them know in the forum. (#{response.code}): #{response.message}"
|
53
|
+
when 502..503
|
54
|
+
raise Unavailable, "(#{response.code}): #{response.message}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_query(options)
|
59
|
+
query_string = options.inject([]) do |collection, opt|
|
60
|
+
collection << "#{opt[0]}=#{opt[1]}"
|
61
|
+
collection
|
62
|
+
end * '&'
|
63
|
+
URI.escape(query_string)
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_uri(path, options)
|
67
|
+
uri = URI.parse(path)
|
68
|
+
|
69
|
+
if options && options != {}
|
70
|
+
uri.query = to_query(options)
|
71
|
+
end
|
72
|
+
uri.to_s
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
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
|