linkedin 0.1.7 → 0.2.1

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.
Files changed (66) hide show
  1. data/.autotest +14 -0
  2. data/.document +5 -0
  3. data/.gitignore +25 -0
  4. data/Gemfile +3 -0
  5. data/README.markdown +12 -7
  6. data/Rakefile +10 -33
  7. data/VERSION +1 -1
  8. data/changelog.markdown +38 -0
  9. data/examples/authenticate.rb +1 -1
  10. data/lib/linked_in/api_standard_profile_request.rb +14 -6
  11. data/lib/linked_in/authorization_helpers.rb +48 -0
  12. data/lib/linked_in/base.rb +13 -0
  13. data/lib/linked_in/birthdate.rb +21 -0
  14. data/lib/linked_in/client.rb +86 -146
  15. data/lib/linked_in/company.rb +9 -7
  16. data/lib/linked_in/connections.rb +14 -5
  17. data/lib/linked_in/country.rb +7 -5
  18. data/lib/linked_in/current_share.rb +56 -0
  19. data/lib/linked_in/education.rb +40 -14
  20. data/lib/linked_in/error.rb +19 -8
  21. data/lib/linked_in/group.rb +30 -7
  22. data/lib/linked_in/languages.rb +28 -0
  23. data/lib/linked_in/likes.rb +23 -0
  24. data/lib/linked_in/location.rb +11 -6
  25. data/lib/linked_in/message.rb +20 -0
  26. data/lib/linked_in/network.rb +10 -5
  27. data/lib/linked_in/patents.rb +42 -0
  28. data/lib/linked_in/people.rb +16 -8
  29. data/lib/linked_in/person.rb +7 -0
  30. data/lib/linked_in/phone_number.rb +29 -0
  31. data/lib/linked_in/position.rb +45 -14
  32. data/lib/linked_in/profile.rb +81 -28
  33. data/lib/linked_in/publications.rb +40 -0
  34. data/lib/linked_in/recipient.rb +7 -0
  35. data/lib/linked_in/recipients.rb +18 -0
  36. data/lib/linked_in/recommendations.rb +30 -0
  37. data/lib/linked_in/request_helpers.rb +76 -0
  38. data/lib/linked_in/short_profile.rb +13 -0
  39. data/lib/linked_in/skill.rb +33 -0
  40. data/lib/linked_in/to_xml_helpers.rb +53 -0
  41. data/lib/linked_in/update.rb +21 -9
  42. data/lib/linked_in/url_resource.rb +24 -6
  43. data/lib/linkedin.rb +58 -59
  44. data/linkedin.gemspec +52 -0
  45. data/spec/cases/client_spec.rb +281 -0
  46. data/spec/cases/linkedin_spec.rb +37 -0
  47. data/spec/cases/oauth_spec.rb +109 -0
  48. data/{test → spec}/fixtures/blank.xml +0 -0
  49. data/{test → spec}/fixtures/connections.xml +0 -0
  50. data/{test → spec}/fixtures/error.xml +0 -0
  51. data/spec/fixtures/likes.xml +18 -0
  52. data/spec/fixtures/mailbox_items.xml +16 -0
  53. data/{test → spec}/fixtures/network_status_with_group.xml +3 -3
  54. data/{test → spec}/fixtures/network_statuses.xml +18 -0
  55. data/{test → spec}/fixtures/picture_updates.xml +0 -0
  56. data/{test → spec}/fixtures/profile.xml +0 -0
  57. data/{test → spec}/fixtures/profile_full.xml +57 -0
  58. data/{test → spec}/fixtures/profile_with_positions.xml +0 -0
  59. data/{test → spec}/fixtures/search.xml +2 -2
  60. data/spec/fixtures/shares.xml +12 -0
  61. data/{test → spec}/fixtures/status.xml +0 -0
  62. data/spec/spec_helper.rb +49 -0
  63. metadata +160 -76
  64. data/test/client_test.rb +0 -124
  65. data/test/oauth_test.rb +0 -117
  66. data/test/test_helper.rb +0 -51
@@ -1,15 +1,46 @@
1
1
  module LinkedIn
2
- class Position
3
- include ROXML
4
- xml_convention {|val| val.gsub("_","-") }
5
- xml_reader :id
6
- xml_reader :title
7
- xml_reader :summary
8
- xml_reader :start_year, :from => "start-date/year", :as => Integer
9
- xml_reader :start_month, :from => "start-date/month", :as => Integer
10
- xml_reader :end_year, :from => "end-date/year", :as => Integer
11
- xml_reader :end_month, :from => "end-date/month", :as => Integer
12
- xml_reader :is_current
13
- xml_reader :company, :as => Company
14
- end
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
@@ -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,7 @@
1
+ module LinkedIn
2
+ class Recipient
3
+
4
+ attr_accessor :person
5
+
6
+ end
7
+ 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,13 @@
1
+ module LinkedIn
2
+
3
+ class ShortProfile < LinkedIn::Base
4
+
5
+ %w[id first_name last_name].each do |f|
6
+ define_method(f.to_sym) do
7
+ @doc.xpath("./#{f.gsub(/_/,'-')}").text
8
+ end
9
+ end
10
+
11
+ end
12
+
13
+ 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