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.
Files changed (73) hide show
  1. data/.autotest +14 -0
  2. data/.document +5 -0
  3. data/.gitignore +25 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +20 -0
  6. data/README.markdown +80 -0
  7. data/Rakefile +41 -0
  8. data/VERSION +1 -0
  9. data/changelog.markdown +76 -0
  10. data/examples/authenticate.rb +21 -0
  11. data/examples/network.rb +12 -0
  12. data/examples/profile.rb +12 -0
  13. data/examples/status.rb +9 -0
  14. data/lib/linked_in/api_standard_profile_request.rb +17 -0
  15. data/lib/linked_in/authorization_helpers.rb +48 -0
  16. data/lib/linked_in/base.rb +13 -0
  17. data/lib/linked_in/birthdate.rb +21 -0
  18. data/lib/linked_in/client.rb +154 -0
  19. data/lib/linked_in/company.rb +11 -0
  20. data/lib/linked_in/connections.rb +16 -0
  21. data/lib/linked_in/country.rb +9 -0
  22. data/lib/linked_in/current_share.rb +56 -0
  23. data/lib/linked_in/education.rb +41 -0
  24. data/lib/linked_in/error.rb +21 -0
  25. data/lib/linked_in/group.rb +32 -0
  26. data/lib/linked_in/im_account.rb +28 -0
  27. data/lib/linked_in/languages.rb +28 -0
  28. data/lib/linked_in/likes.rb +23 -0
  29. data/lib/linked_in/location.rb +13 -0
  30. data/lib/linked_in/message.rb +20 -0
  31. data/lib/linked_in/network.rb +12 -0
  32. data/lib/linked_in/patents.rb +42 -0
  33. data/lib/linked_in/people.rb +18 -0
  34. data/lib/linked_in/person.rb +7 -0
  35. data/lib/linked_in/phone_number.rb +29 -0
  36. data/lib/linked_in/position.rb +46 -0
  37. data/lib/linked_in/profile.rb +94 -0
  38. data/lib/linked_in/publications.rb +40 -0
  39. data/lib/linked_in/recipient.rb +7 -0
  40. data/lib/linked_in/recipients.rb +18 -0
  41. data/lib/linked_in/recommendations.rb +30 -0
  42. data/lib/linked_in/request_helpers.rb +78 -0
  43. data/lib/linked_in/short_profile.rb +13 -0
  44. data/lib/linked_in/skill.rb +33 -0
  45. data/lib/linked_in/to_xml_helpers.rb +53 -0
  46. data/lib/linked_in/twitter_account.rb +28 -0
  47. data/lib/linked_in/update.rb +23 -0
  48. data/lib/linked_in/url_resource.rb +26 -0
  49. data/lib/linkedin.rb +83 -0
  50. data/linkedin.gemspec +49 -0
  51. data/spec/cases/client_spec.rb +230 -0
  52. data/spec/cases/linkedin_spec.rb +37 -0
  53. data/spec/cases/oauth_spec.rb +109 -0
  54. data/spec/client_shared_examples.orig.rb +91 -0
  55. data/spec/client_shared_examples.rb +104 -0
  56. data/spec/fixtures/403.xml +7 -0
  57. data/spec/fixtures/404.xml +7 -0
  58. data/spec/fixtures/blank.xml +0 -0
  59. data/spec/fixtures/connections.xml +3733 -0
  60. data/spec/fixtures/likes.xml +18 -0
  61. data/spec/fixtures/mailbox_items.xml +16 -0
  62. data/spec/fixtures/network_status_with_group.xml +44 -0
  63. data/spec/fixtures/network_statuses.xml +317 -0
  64. data/spec/fixtures/picture_updates.xml +117 -0
  65. data/spec/fixtures/profile.xml +9 -0
  66. data/spec/fixtures/profile_full.orig.xml +3909 -0
  67. data/spec/fixtures/profile_full.xml +395 -0
  68. data/spec/fixtures/profile_with_positions.xml +79 -0
  69. data/spec/fixtures/search.xml +538 -0
  70. data/spec/fixtures/shares.xml +12 -0
  71. data/spec/fixtures/status.xml +2 -0
  72. data/spec/spec_helper.rb +58 -0
  73. 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,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,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,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
@@ -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