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.
- 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
|