linkedin-bdigital 0.2.2
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 +78 -0
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/changelog.markdown +71 -0
- data/examples/authenticate.rb +21 -0
- data/examples/network.rb +12 -0
- data/examples/profile.rb +14 -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 +155 -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/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 +85 -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/update.rb +23 -0
- data/lib/linked_in/url_resource.rb +26 -0
- data/lib/linkedin.rb +81 -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.rb +91 -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.xml +3909 -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 +179 -0
@@ -0,0 +1,155 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Client
|
3
|
+
include ToXmlHelpers
|
4
|
+
include RequestHelpers
|
5
|
+
include AuthorizationHelpers
|
6
|
+
|
7
|
+
attr_reader :ctoken, :csecret, :consumer_options
|
8
|
+
|
9
|
+
def initialize(ctoken=LinkedIn.token, csecret=LinkedIn.secret, options={})
|
10
|
+
opts = {
|
11
|
+
:request_token_path => "/uas/oauth/requestToken",
|
12
|
+
:access_token_path => "/uas/oauth/accessToken",
|
13
|
+
:authorize_path => "/uas/oauth/authorize"
|
14
|
+
}
|
15
|
+
@ctoken, @csecret, @consumer_options = ctoken, csecret, opts.merge(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def profile(options={})
|
20
|
+
path = person_path(options)
|
21
|
+
fields = options[:fields] || LinkedIn.default_profile_fields
|
22
|
+
|
23
|
+
if options[:public]
|
24
|
+
path +=":public"
|
25
|
+
elsif fields
|
26
|
+
path +=":(#{fields.map{ |f| f.to_s.gsub("_","-") }.join(',')})"
|
27
|
+
end
|
28
|
+
|
29
|
+
Profile.from_xml(get(path))
|
30
|
+
end
|
31
|
+
|
32
|
+
def connections(options={})
|
33
|
+
path = "#{person_path(options)}/connections"
|
34
|
+
fields = options[:fields] || LinkedIn.default_profile_fields
|
35
|
+
|
36
|
+
if options[:public]
|
37
|
+
path +=":public"
|
38
|
+
elsif fields
|
39
|
+
path +=":(#{fields.map{ |f| f.to_s.gsub("_","-") }.join(',')})"
|
40
|
+
end
|
41
|
+
|
42
|
+
Connections.from_xml(get(path)).profiles
|
43
|
+
end
|
44
|
+
|
45
|
+
def search(options={})
|
46
|
+
path = "/people"
|
47
|
+
options = { :keywords => options } if options.is_a?(String)
|
48
|
+
options = format_options_for_query(options)
|
49
|
+
|
50
|
+
People.from_xml(get(to_uri(path, options)))
|
51
|
+
end
|
52
|
+
|
53
|
+
def current_status
|
54
|
+
path = "/people/~/current-status"
|
55
|
+
Nokogiri::XML(get(path)).xpath('//current-status').text
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_status(text)
|
59
|
+
path = "/people/~/current-status"
|
60
|
+
put(path, status_to_xml(text))
|
61
|
+
end
|
62
|
+
|
63
|
+
def share(options={})
|
64
|
+
path = "/people/~/shares"
|
65
|
+
defaults = { :visability => 'anyone' }
|
66
|
+
post(path, share_to_xml(defaults.merge(options)))
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_comment(network_key, comment)
|
70
|
+
path = "/people/~/network/updates/key=#{network_key}/update-comments"
|
71
|
+
post(path, comment_to_xml(comment))
|
72
|
+
end
|
73
|
+
|
74
|
+
def like(network_key, is_liked=true)
|
75
|
+
path = "/people/~/network/updates/key=#{network_key}/is-liked"
|
76
|
+
put(path, is_liked_to_xml(is_liked))
|
77
|
+
end
|
78
|
+
|
79
|
+
def likes(network_key)
|
80
|
+
path = "/people/~/network/updates/key=#{network_key}/likes"
|
81
|
+
Likes.from_xml(get(path)).likes
|
82
|
+
end
|
83
|
+
|
84
|
+
def update_network(message)
|
85
|
+
path = "/people/~/person-activities"
|
86
|
+
post(path, network_update_to_xml(message))
|
87
|
+
end
|
88
|
+
|
89
|
+
def clear_status
|
90
|
+
path = "/people/~/current-status"
|
91
|
+
delete(path).code
|
92
|
+
end
|
93
|
+
|
94
|
+
def send_message(subject, body, recipient_paths)
|
95
|
+
path = "/people/~/mailbox"
|
96
|
+
|
97
|
+
message = LinkedIn::Message.new
|
98
|
+
message.subject = subject
|
99
|
+
message.body = body
|
100
|
+
recipients = LinkedIn::Recipients.new
|
101
|
+
|
102
|
+
recipients.recipients = recipient_paths.map do |profile_path|
|
103
|
+
recipient = LinkedIn::Recipient.new
|
104
|
+
recipient.person = LinkedIn::Person.new
|
105
|
+
recipient.person.path = "/people/#{profile_path}"
|
106
|
+
recipient
|
107
|
+
end
|
108
|
+
|
109
|
+
message.recipients = recipients
|
110
|
+
post(path, message_to_xml(message)).code
|
111
|
+
end
|
112
|
+
|
113
|
+
def network_statuses(options={})
|
114
|
+
options[:type] = 'STAT'
|
115
|
+
network_updates(options)
|
116
|
+
end
|
117
|
+
|
118
|
+
def network_updates(options={})
|
119
|
+
path = "/people/~/network"
|
120
|
+
Network.from_xml(get(to_uri(path, options)))
|
121
|
+
end
|
122
|
+
|
123
|
+
# helpful in making authenticated calls and writing the
|
124
|
+
# raw xml to a fixture file
|
125
|
+
def write_fixture(path, filename)
|
126
|
+
file = File.new("test/fixtures/#{filename}", "w")
|
127
|
+
file.puts(access_token.get(path).body)
|
128
|
+
file.close
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def format_options_for_query(opts)
|
134
|
+
opts.keys.each do |key|
|
135
|
+
value = opts.delete(key)
|
136
|
+
value = value.join("+") if value.is_a?(Array)
|
137
|
+
value = value.gsub(" ", "+") if value.is_a?(String)
|
138
|
+
opts[key.to_s.gsub("_","-")] = value
|
139
|
+
end
|
140
|
+
opts
|
141
|
+
end
|
142
|
+
|
143
|
+
def person_path(options)
|
144
|
+
path = "/people/"
|
145
|
+
if options[:id]
|
146
|
+
path += "id=#{options[:id]}"
|
147
|
+
elsif options[:url]
|
148
|
+
path += "url=#{CGI.escape(options[:url])}"
|
149
|
+
else
|
150
|
+
path += "~"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Connections < LinkedIn::Base
|
3
|
+
|
4
|
+
def connections
|
5
|
+
@connections ||= @doc.xpath('//connections').children.inject([]) do |list, profile|
|
6
|
+
list << Profile.new(Nokogiri::XML(profile.to_xml)) unless profile.blank?
|
7
|
+
list
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def profiles
|
12
|
+
connections
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
|
3
|
+
class CurrentShare < LinkedIn::Base
|
4
|
+
%w[id comment].each do |f|
|
5
|
+
define_method(f.to_sym) do
|
6
|
+
@doc.xpath("./#{f.gsub(/_/,'-')}").text
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def submitted_url
|
11
|
+
@doc.xpath('./content/submitted-url').text
|
12
|
+
end
|
13
|
+
|
14
|
+
def resolved_url
|
15
|
+
@doc.xpath('./content/resolved-url').text
|
16
|
+
end
|
17
|
+
|
18
|
+
def shortened_url
|
19
|
+
@doc.xpath('./content/shortened-url').text
|
20
|
+
end
|
21
|
+
|
22
|
+
def title
|
23
|
+
@doc.xpath('./content/title').text
|
24
|
+
end
|
25
|
+
|
26
|
+
def description
|
27
|
+
@doc.xpath('./content/description').text
|
28
|
+
end
|
29
|
+
|
30
|
+
def submitted_image_url
|
31
|
+
@doc.xpath('./content/submitted-image-url').text
|
32
|
+
end
|
33
|
+
|
34
|
+
def thumbnail_url
|
35
|
+
@doc.xpath('./content/thumbnail-url').text
|
36
|
+
end
|
37
|
+
|
38
|
+
def timestamp
|
39
|
+
time = @doc.xpath('./timestamp').text.to_i
|
40
|
+
Time.at(time / 1000)
|
41
|
+
end
|
42
|
+
|
43
|
+
def visibility
|
44
|
+
@doc.xpath('./visibility/code').text
|
45
|
+
end
|
46
|
+
|
47
|
+
def application
|
48
|
+
@doc.xpath('./source/application/name').text
|
49
|
+
end
|
50
|
+
|
51
|
+
def author
|
52
|
+
@author ||= ShortProfile.new(@doc.xpath('./author'))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Education < LinkedIn::Base
|
3
|
+
|
4
|
+
def educations
|
5
|
+
@educations ||= @doc.children.inject([]) do |list, edu|
|
6
|
+
list << Resource.new(edu) unless edu.blank?
|
7
|
+
list
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Resource
|
12
|
+
|
13
|
+
def initialize(education)
|
14
|
+
@education = education
|
15
|
+
end
|
16
|
+
|
17
|
+
%w[id school_name degree field_of_study activities notes].each do |f|
|
18
|
+
define_method(f.to_sym) do
|
19
|
+
@education.xpath("./#{f.gsub(/_/,'-')}").text
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def start_month
|
24
|
+
@education.xpath('./start-date/month').text.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def start_year
|
28
|
+
@education.xpath('./start-date/year').text.to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
def end_month
|
32
|
+
@education.xpath('./end-date/month').text.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
def end_year
|
36
|
+
@education.xpath('./end-date/year').text.to_i
|
37
|
+
end
|
38
|
+
end # resource class
|
39
|
+
|
40
|
+
end # education class
|
41
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Error < LinkedIn::Base
|
3
|
+
|
4
|
+
def status
|
5
|
+
@doc.xpath('//status').text.to_i
|
6
|
+
end
|
7
|
+
|
8
|
+
def timestamp
|
9
|
+
@doc.xpath('//timestamp').text.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
def code
|
13
|
+
@doc.xpath('//error-code').text.to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
def message
|
17
|
+
@doc.xpath('//message').text
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Group < LinkedIn::Base
|
3
|
+
|
4
|
+
def groups
|
5
|
+
@groups ||= @doc.children.inject([]) do |list, group|
|
6
|
+
list << Resource.new(group) unless group.blank?
|
7
|
+
list
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Resource
|
12
|
+
|
13
|
+
def initialize(group)
|
14
|
+
@group = group
|
15
|
+
end
|
16
|
+
|
17
|
+
def id
|
18
|
+
@group.xpath('//member-group/id').text.to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
@group.xpath('//member-group/name').text
|
23
|
+
end
|
24
|
+
|
25
|
+
def url
|
26
|
+
@group.xpath('//member-group/site-group-request/url').text
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Languages < LinkedIn::Base
|
3
|
+
|
4
|
+
def languages
|
5
|
+
@languages ||= @doc.children.inject([]) do |list, lang|
|
6
|
+
list << Resource.new(lang) unless lang.blank?
|
7
|
+
list
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Resource
|
12
|
+
# proficiency level not implemented yet
|
13
|
+
# http://developer.linkedin.com/community/apis/blog/2011/01/04/new-profile-fields-are-here
|
14
|
+
def initialize(language)
|
15
|
+
@language = language
|
16
|
+
end
|
17
|
+
|
18
|
+
def id
|
19
|
+
@language.xpath("./id").text
|
20
|
+
end
|
21
|
+
|
22
|
+
def name
|
23
|
+
@language.xpath("./language/name").text
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Likes < LinkedIn::Base
|
3
|
+
|
4
|
+
def likes
|
5
|
+
@likes ||= @doc.xpath('//likes').children.inject([]) do |list, like|
|
6
|
+
list << Resource.new(like) unless like.blank?
|
7
|
+
list
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Resource
|
12
|
+
def initialize(like)
|
13
|
+
@like = like
|
14
|
+
end
|
15
|
+
|
16
|
+
def profile
|
17
|
+
Profile.new(Nokogiri::XML(@like.xpath('./person').to_xml))
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
class Message
|
3
|
+
|
4
|
+
attr_accessor :subject, :body, :recipients
|
5
|
+
|
6
|
+
def to_xml
|
7
|
+
self.to_xml_node(Nokogiri.XML('<root/>', nil, 'UTF-8')).to_xml
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_xml_node(doc)
|
11
|
+
node = Nokogiri::XML::DocumentFragment.new(doc, '<mailbox-item><recipients/><subject/><body/></mailbox-item>')
|
12
|
+
node.at_css('recipients').add_child(self.recipients.to_xml_nodes(doc))
|
13
|
+
node.at_css('subject').content = self.subject
|
14
|
+
node.at_css('body').content = self.body
|
15
|
+
node
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|