linkedin-v2 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +10 -0
  3. data/CONTRIBUTING.md +1 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +22 -0
  6. data/README.md +224 -0
  7. data/Rakefile +19 -0
  8. data/lib/linked_in/access_token.rb +24 -0
  9. data/lib/linked_in/api.rb +108 -0
  10. data/lib/linked_in/api_resource.rb +180 -0
  11. data/lib/linked_in/communications.rb +40 -0
  12. data/lib/linked_in/configuration.rb +41 -0
  13. data/lib/linked_in/connection.rb +35 -0
  14. data/lib/linked_in/errors.rb +73 -0
  15. data/lib/linked_in/jobs.rb +11 -0
  16. data/lib/linked_in/mash.rb +68 -0
  17. data/lib/linked_in/media.rb +13 -0
  18. data/lib/linked_in/oauth2.rb +223 -0
  19. data/lib/linked_in/organizations.rb +217 -0
  20. data/lib/linked_in/people.rb +151 -0
  21. data/lib/linked_in/raise_error.rb +28 -0
  22. data/lib/linked_in/search.rb +70 -0
  23. data/lib/linked_in/share_and_social_stream.rb +143 -0
  24. data/lib/linked_in/version.rb +3 -0
  25. data/lib/linkedin-v2.rb +52 -0
  26. data/linkedin-v2.gemspec +39 -0
  27. data/pkg/linkedin-oauth2-2.0.0.gem +0 -0
  28. data/spec/linked_in/api/api_spec.rb +41 -0
  29. data/spec/linked_in/api/communications_spec.rb +13 -0
  30. data/spec/linked_in/api/jobs_spec.rb +33 -0
  31. data/spec/linked_in/api/organizations_spec.rb +54 -0
  32. data/spec/linked_in/api/people_spec.rb +191 -0
  33. data/spec/linked_in/api/search_spec.rb +71 -0
  34. data/spec/linked_in/api/share_and_social_stream_spec.rb +87 -0
  35. data/spec/linked_in/configuration_spec.rb +46 -0
  36. data/spec/linked_in/connection_spec.rb +10 -0
  37. data/spec/linked_in/module_loading_spec.rb +23 -0
  38. data/spec/linked_in/oauth/access_token_spec.rb +27 -0
  39. data/spec/linked_in/oauth/auth_code_spec.rb +86 -0
  40. data/spec/linked_in/oauth/credentials_spec.rb +96 -0
  41. data/spec/linked_in/oauth/get_access_token_spec.rb +108 -0
  42. data/spec/spec_helper.rb +16 -0
  43. data/spec/vcr_cassettes/access_token_success.yml +99 -0
  44. data/spec/vcr_cassettes/bad_code.yml +99 -0
  45. data/spec/vcr_cassettes/organization_data.yml +51 -0
  46. data/spec/vcr_cassettes/people_picture_urls.yml +52 -0
  47. data/spec/vcr_cassettes/people_profile_connections_fields.yml +52 -0
  48. data/spec/vcr_cassettes/people_profile_connections_other.yml +52 -0
  49. data/spec/vcr_cassettes/people_profile_connections_self.yml +52 -0
  50. data/spec/vcr_cassettes/people_profile_fields_complex.yml +52 -0
  51. data/spec/vcr_cassettes/people_profile_fields_simple.yml +52 -0
  52. data/spec/vcr_cassettes/people_profile_lang_spanish.yml +53 -0
  53. data/spec/vcr_cassettes/people_profile_multiple_fields.yml +52 -0
  54. data/spec/vcr_cassettes/people_profile_multiple_uids.yml +52 -0
  55. data/spec/vcr_cassettes/people_profile_multiple_uids_and_urls.yml +52 -0
  56. data/spec/vcr_cassettes/people_profile_multiple_urls.yml +52 -0
  57. data/spec/vcr_cassettes/people_profile_new_connections_fields.yml +52 -0
  58. data/spec/vcr_cassettes/people_profile_new_connections_other.yml +52 -0
  59. data/spec/vcr_cassettes/people_profile_new_connections_self.yml +52 -0
  60. data/spec/vcr_cassettes/people_profile_other_uid.yml +57 -0
  61. data/spec/vcr_cassettes/people_profile_other_url.yml +54 -0
  62. data/spec/vcr_cassettes/people_profile_own.yml +57 -0
  63. data/spec/vcr_cassettes/people_profile_own_secure.yml +53 -0
  64. data/spec/vcr_cassettes/people_profile_skills.yml +52 -0
  65. data/spec/vcr_cassettes/unavailable.yml +99 -0
  66. metadata +285 -0
@@ -0,0 +1,217 @@
1
+ module LinkedIn
2
+ # Organizations API
3
+ #
4
+ # @see https://developer.linkedin.com/docs/guide/v2/organizations
5
+ #
6
+ # [(contribute here)](https://github.com/mdesjardins/linkedin-v2)
7
+ class Organizations < APIResource
8
+ # Retrieve an Organization
9
+ #
10
+ # @see https://developer.linkedin.com/docs/guide/v2/organizations/organization-lookup-api
11
+ #
12
+ # @macro organization_path_options
13
+ # @option options [String] :scope
14
+ # @option options [String] :type
15
+ # @option options [String] :count
16
+ # @option options [String] :start
17
+ # @return [LinkedIn::Mash]
18
+ def organization(options = {})
19
+ path = organization_path(options)
20
+ get(path, options)
21
+ end
22
+
23
+ # Retrieve an Organization Brand
24
+ #
25
+ # @see https://developer.linkedin.com/docs/guide/v2/organizations/organization-lookup-api
26
+ #
27
+ # @macro brand_path_options
28
+ # @option options [String] :scope
29
+ # @option options [String] :type
30
+ # @option options [String] :count
31
+ # @option options [String] :start
32
+ # @return [LinkedIn::Mash]
33
+ def brand(options = {})
34
+ path = brand_path(options)
35
+ get(path, options)
36
+ end
37
+
38
+ # Retrieve Organization Access Control informaion
39
+ #
40
+ # @see https://developer.linkedin.com/docs/guide/v2/organizations/organization-lookup-api#acls
41
+ #
42
+ def organization_acls(options = {})
43
+ path = '/organizationalEntityAcls'
44
+ get(path, options)
45
+ end
46
+
47
+ # Perform a keyword-based Organization search sorted by relevance
48
+ #
49
+ # @see https://developer.linkedin.com/docs/guide/v2/organizations/organization-search
50
+ #
51
+ # @macro organization_path_options
52
+ # @option options [String] :scope
53
+ # @option options [String] :type
54
+ # @option options [String] :count
55
+ # @option options [String] :start
56
+ # @return [LinkedIn::Mash]
57
+ def organization_search(options = {})
58
+ path = "/search?q=companiesV2&baseSearchParams.keywords=#{CGI.escape(options[:keyword])}&projection=(metadata,elements*(entity~),paging)"
59
+ get(path, options)
60
+ end
61
+
62
+ # TODO MOVE TO SHARES FOR EVERYTHING.
63
+ # # Retrieve a feed of event shares for an Organization
64
+ # #
65
+ # # @see http://developer.linkedin.com/reading-company-shares
66
+ # #
67
+ # # @macro organization_path_options
68
+ # # @option options [String] :event-type
69
+ # # @option options [String] :count
70
+ # # @option options [String] :start
71
+ # # @return [LinkedIn::Mash]
72
+ # def organization_shares(options={})
73
+ # path = "#{organization_path(options)}/updates"
74
+ # get(path, options)
75
+ # end
76
+
77
+ # Retrieve statistics for a particular organization page
78
+ #
79
+ # Permissions: rw_organization
80
+ #
81
+ # @see https://developer.linkedin.com/docs/guide/v2/organizations/page-statistics
82
+ #
83
+ # @option urn [String] organization URN
84
+ # @return [LinkedIn::Mash]
85
+ def organization_page_statistics(options = {})
86
+ path = "/organizationPageStatistics?q=organization&organization=#{options.delete(:urn)}"
87
+ get(path, options)
88
+ end
89
+
90
+ # Retrieve statistics for a particular organization followers
91
+ #
92
+ # Permissions: rw_organization
93
+ #
94
+ # @see https://developer.linkedin.com/docs/guide/v2/organizations/follower-statistics
95
+ #
96
+ # @option urn [String] organization URN
97
+ # @return [LinkedIn::Mash]
98
+ def organization_follower_statistics(options = {})
99
+ path = "/organizationalEntityFollowerStatistics?q=organizationalEntity&organizationalEntity=#{options.delete(:urn)}"
100
+ get(path, options)
101
+ end
102
+
103
+ # Retrieve statistics for a particular organization shares
104
+ #
105
+ # Permissions: rw_organization
106
+ #
107
+ # @see https://developer.linkedin.com/docs/guide/v2/organizations/share-statistics
108
+ #
109
+ # @option urn [String] organization URN
110
+ # @return [LinkedIn::Mash]
111
+ def organization_share_statistics(options = {})
112
+ path = "/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=#{options.delete(:urn)}"
113
+ get(path, options)
114
+ end
115
+
116
+ # TODO MOVE TO SOCIAL ACTIONS.
117
+ #
118
+ # # Retrieve comments on a particular company update:
119
+ # #
120
+ # # @see http://developer.linkedin.com/reading-company-shares
121
+ # #
122
+ # # @param [String] update_key a update/update-key representing a
123
+ # # particular company update
124
+ # # @macro organization_path_options
125
+ # # @return [LinkedIn::Mash]
126
+ # def company_updates_comments(update_key, options={})
127
+ # path = "#{organization_path(options)}/updates/key=#{update_key}/update-comments"
128
+ # get(path, options)
129
+ # end
130
+
131
+ # # Retrieve likes on a particular company update:
132
+ # #
133
+ # # @see http://developer.linkedin.com/reading-company-shares
134
+ # #
135
+ # # @param [String] update_key a update/update-key representing a
136
+ # # particular company update
137
+ # # @macro organization_path_options
138
+ # # @return [LinkedIn::Mash]
139
+ # def company_updates_likes(update_key, options={})
140
+ # path = "#{organization_path(options)}/updates/key=#{update_key}/likes"
141
+ # get(path, options)
142
+ # end
143
+
144
+ # # Create a share for a company that the authenticated user
145
+ # # administers
146
+ # #
147
+ # # Permissions: rw_company_admin
148
+ # #
149
+ # # @see http://developer.linkedin.com/creating-company-shares
150
+ # # @see http://developer.linkedin.com/documents/targeting-company-shares Targeting Company Shares
151
+ # #
152
+ # # @param [String] company_id Company ID
153
+ # # @macro share_input_fields
154
+ # # @return [void]
155
+ # def add_company_share(company_id, share)
156
+ # path = "/companies/#{company_id}/shares?format=json"
157
+ # defaults = {visibility: {code: "anyone"}}
158
+ # post(path, MultiJson.dump(defaults.merge(share)), "Content-Type" => "application/json")
159
+ # end
160
+
161
+ # # (Create) authenticated user starts following a company
162
+ # #
163
+ # # @see http://developer.linkedin.com/documents/company-follow-and-suggestions
164
+ # #
165
+ # # @param [String] company_id Company ID
166
+ # # @return [void]
167
+ # def follow_company(company_id)
168
+ # path = "/people/~/following/companies"
169
+ # post(path, {id: company_id})
170
+ # end
171
+
172
+ # # (Destroy) authenticated user stops following a company
173
+ # #
174
+ # # @see http://developer.linkedin.com/documents/company-follow-and-suggestions
175
+ # #
176
+ # # @param [String] company_id Company ID
177
+ # # @return [void]
178
+ # def unfollow_company(company_id)
179
+ # path = "/people/~/following/companies/id=#{company_id}"
180
+ # delete(path)
181
+ # end
182
+
183
+
184
+ private ##############################################################
185
+
186
+
187
+ def organization_path(options)
188
+ path = '/organizations'
189
+
190
+ if email_domain = options.delete(:email_domain)
191
+ path += "?q=emailDomain&emailDomain=#{CGI.escape(email_domain)}"
192
+ elsif id = options.delete(:id)
193
+ path += "/#{id}"
194
+ elsif urn = options.delete(:urn)
195
+ path += "/#{urn_to_id(urn)}"
196
+ elsif vanity_name = options.delete(:vanity_name)
197
+ path += "?q=vanityName&vanityName=#{CGI.escape(vanity_name)}"
198
+ else
199
+ path += "/me"
200
+ end
201
+ end
202
+
203
+ def brand_path(options)
204
+ path = '/organizationBrands'
205
+
206
+ if id = options.delete(:id)
207
+ path += "/#{id}"
208
+ elsif vanity_name = options.delete(:vanity_name)
209
+ path += ">q=vanityName&vanityName=#{CGI.escape(vanity_name)}"
210
+ elsif parent_id = options.delete(:parent_id)
211
+ path = "/organizations?q=parentOrganization&parent=#{CGI.escape(parent_id)}"
212
+ else
213
+ path += "/me"
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,151 @@
1
+ require 'time'
2
+ module LinkedIn
3
+ # People APIs
4
+ #
5
+ # @see http://developer.linkedin.com/documents/people People API
6
+ # @see http://developer.linkedin.com/documents/profile-fields Profile Fields
7
+ # @see http://developer.linkedin.com/documents/field-selectors Field Selectors
8
+ # @see http://developer.linkedin.com/documents/accessing-out-network-profiles Accessing Out of Network Profiles
9
+ class People < APIResource
10
+
11
+ # @!macro multi_profile_options
12
+ # @options opts [Array] :urls A list of profile urls
13
+ # @options opts [Array] :ids List of LinkedIn IDs
14
+ #
15
+ # @!macro new profile_args
16
+ # @overload profile()
17
+ # Fetches your own profile
18
+ # @overload profile(id_or_url, opts)
19
+ # Fetches the profile of another user
20
+ # @param [String] id_or_url a LinkedIn id or a profile URL
21
+ # @param [Hash] opts more profile options
22
+ # @macro profile_options
23
+ # @overload profile(opts)
24
+ # Fetches the profile of another user
25
+ # @param [Hash] opts profile options
26
+ # @macro profile_options
27
+ # @return [LinkedIn::Mash]
28
+
29
+ # Retrieve a member's LinkedIn profile.
30
+ #
31
+ # Required permissions: r_basicprofile, r_fullprofile
32
+ #
33
+ # @see http://developer.linkedin.com/documents/profile-api
34
+ # @macro profile_args
35
+ # @macro multi_profile_options
36
+ def profile(id={}, options={})
37
+ options = parse_id(id, options)
38
+ path = profile_path(options)
39
+ get(path, options)
40
+ end
41
+
42
+ # Retrieve a list of 1st degree connections for a user who has
43
+ # granted access to his/her account
44
+ #
45
+ # Permissions: r_network
46
+ #
47
+ # @see http://developer.linkedin.com/documents/connections-api
48
+ # @macro profile_args
49
+ def connections(id={}, options={})
50
+ options = parse_id(id, options)
51
+ path = "#{profile_path(options, false)}/connections"
52
+ get(path, options)
53
+ end
54
+
55
+ # Retrieve a list of the latest set of 1st degree connections for a
56
+ # user
57
+ #
58
+ # Permissions: r_network
59
+ #
60
+ # @see http://developer.linkedin.com/documents/connections-api
61
+ #
62
+ # @param [String, Fixnum, Time] modified_since timestamp in unix time
63
+ # miliseconds indicating since when you want to retrieve new
64
+ # connections
65
+ # @param [Hash] opts profile options
66
+ # @macro profile_options
67
+ # @return [LinkedIn::Mash]
68
+ def new_connections(since, options={})
69
+ since = parse_modified_since(since)
70
+ options.merge!('modified' => 'new', 'modified-since' => since)
71
+ path = "#{profile_path(options, false)}/connections"
72
+ get(path, options)
73
+ end
74
+
75
+ # Retrieve the picture url
76
+ # http://api.linkedin.com/v1/people/~/picture-urls::(original)
77
+ #
78
+ # Permissions: r_network
79
+ #
80
+ # @options [String] :id, the id of the person for whom you want the profile picture
81
+ # @options [String] :picture_size, default: 'original'
82
+ # @options [String] :secure, default: 'false', options: ['false','true']
83
+ #
84
+ # example for use in code: client.picture_urls(:id => 'id_of_connection')
85
+ def picture_urls(options={})
86
+ picture_size = options.delete(:picture_size) || 'original'
87
+ path = "#{profile_path(options)}/picture-urls::(#{picture_size})"
88
+ get(path, options)
89
+ end
90
+
91
+ # Retrieve the skills
92
+ #
93
+ # Permissions: r_fullprofile
94
+ def skills(id={}, options={})
95
+ options = parse_id(id, options)
96
+ path = "#{profile_path(options, false)}/skills"
97
+ get(path, options)
98
+ end
99
+
100
+
101
+ protected ############################################################
102
+
103
+
104
+ def get(path, options)
105
+ # TODO LIv2 : Investigate, not sure this is needed, secure is always on?
106
+ # options[:"secure-urls"] = true unless options[:secure] == false
107
+ super path, options
108
+ end
109
+
110
+
111
+ private ##############################################################
112
+
113
+
114
+ def parse_id(id, options)
115
+ if id.is_a? String
116
+ if id.downcase =~ /linkedin\.com/
117
+ options[:url] = id
118
+ else
119
+ options[:id] = id
120
+ end
121
+ elsif id.is_a? Hash
122
+ options = id
123
+ # else
124
+ # options = {}
125
+ end
126
+
127
+ return options
128
+ end
129
+
130
+ # Returns a unix time in miliseconds
131
+ def parse_modified_since(since)
132
+ if since.is_a? ::Fixnum
133
+ if ::Time.at(since).year < 2050
134
+ # Got passed in as seconds.
135
+ since = since * 1000
136
+ end
137
+ elsif since.is_a? ::String
138
+ since = utc_parse(since)
139
+ elsif since.is_a? ::Time
140
+ since = since.to_i * 1000
141
+ end
142
+ return since
143
+ end
144
+
145
+ def utc_parse(since)
146
+ t = ::Time.parse(since)
147
+ Time.utc(t.year, t.month, t.day, t.hour, t.min, t.sec).to_i * 1000
148
+ end
149
+
150
+ end
151
+ end
@@ -0,0 +1,28 @@
1
+ require 'faraday'
2
+
3
+ module LinkedIn
4
+ class RaiseError < Faraday::Response::RaiseError
5
+ def on_complete(response)
6
+ data = Mash.from_json(response.body)
7
+ case response.status.to_i
8
+ when 400
9
+ raise LinkedIn::InvalidRequest.new(data), "(#{data.status}): #{data.message}"
10
+ when 401
11
+ raise LinkedIn::UnauthorizedError.new(data), "(#{data.status}): #{data.message}"
12
+ when 403
13
+ raise LinkedIn::AccessDeniedError.new(data), "(#{data.status}): #{data.message}"
14
+ when 404
15
+ raise LinkedIn::NotFoundError.new(data), "(#{data.status}): #{data.message}"
16
+ when 500
17
+ raise LinkedIn::InformLinkedInError.new(data),
18
+ "LinkedIn had an internal error. Please let them know in the forum. (#{data.status}): #{data.message}"
19
+ when 502..504
20
+ raise LinkedIn::UnavailableError.new(data), "(#{data.status}): #{data.message}"
21
+ else
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ Faraday::Response.register_middleware :linkedin_raise_error => LinkedIn::RaiseError
@@ -0,0 +1,70 @@
1
+ module LinkedIn
2
+ # Search APIs
3
+ #
4
+ # @see https://developer.linkedin.com/documents/people-search-api
5
+ # @see https://developer.linkedin.com/documents/company-search
6
+ # @see https://developer.linkedin.com/documents/job-search-api
7
+ class Search < APIResource
8
+ # Search through People, Companies, and Jobs
9
+ #
10
+ # To search through people you need to be part of LinkedIn's Vetted
11
+ # API Access Program.
12
+ #
13
+ # @see https://developer.linkedin.com/documents/people-search-api
14
+ #
15
+ # You can use the same API to search through Companies and Jobs.
16
+ #
17
+ # @!macro search_options
18
+ # @options opts [String] :type either "people", "company", or
19
+ # "job"
20
+ # @options opts [String] :keywords various keywords to search for
21
+ # @options opts [Array, Hash] :fields fields to fetch. The
22
+ # list of fields can be found at
23
+ # https://developer.linkedin.com/documents/field-selectors
24
+ # @options opts various There are many more options you can search
25
+ # by see https://developer.linkedin.com/documents/people-search-api
26
+ # for more possibilities. All keys can be entered underscored and
27
+ # they will be dasherized to match the field names on LinkedIn's
28
+ # website
29
+ #
30
+ # @overload search
31
+ # Grabs all people in your network
32
+ # @overload search(keyword_string)
33
+ # Keyword search through people
34
+ # @param [String] keywords search keywords
35
+ # @overload search(keyword_string, type)
36
+ # Keyword search through people
37
+ # @param [String] keywords search keywords
38
+ # @param [String] type either "people", "company", or "job"
39
+ # @overload search(opts)
40
+ # Searches based on various options
41
+ # @param [Hash] opts search options
42
+ # @macro search_options
43
+ # @overload search(opts, type)
44
+ # Searches for a type based on various options
45
+ # @param [Hash] opts search options
46
+ # @macro search_options
47
+ # @param [String] type either "people", "company", or "job"
48
+ def search(options={}, type='people')
49
+ options, type = prepare_options(options, type)
50
+ path = "/#{type.to_s}-search"
51
+ get(path, options)
52
+ end
53
+
54
+ private ##############################################################
55
+
56
+ def prepare_options(options, type)
57
+ options ||= {}
58
+ if options.is_a? String or options.is_a? Array
59
+ kw = options
60
+ options = {keywords: kw}
61
+ end
62
+
63
+ if not options[:type].nil?
64
+ type = options.delete(:type)
65
+ end
66
+
67
+ return [options, type]
68
+ end
69
+ end
70
+ end