linkedin-v2 0.1.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 (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