linkedin2 0.0.16 → 0.0.17

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +2 -0
  4. data/.rspec +2 -2
  5. data/.travis.yml +1 -1
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/lib/linkedin/api.rb +16 -0
  9. data/lib/linkedin/api/authentication.rb +5 -7
  10. data/lib/linkedin/api/companies.rb +16 -10
  11. data/lib/linkedin/api/invitation.rb +39 -0
  12. data/lib/linkedin/api/messaging.rb +24 -0
  13. data/lib/linkedin/api/network_updates.rb +5 -9
  14. data/lib/linkedin/api/people.rb +18 -0
  15. data/lib/linkedin/client.rb +34 -72
  16. data/lib/linkedin/configuration.rb +26 -11
  17. data/lib/linkedin/credentials.rb +27 -0
  18. data/lib/linkedin/errors.rb +45 -0
  19. data/lib/linkedin/faraday_middleware.rb +6 -4
  20. data/lib/linkedin/faraday_middleware/credentials_request.rb +31 -0
  21. data/lib/linkedin/faraday_middleware/{linkedin_error_response.rb → error_response.rb} +2 -10
  22. data/lib/linkedin/faraday_middleware/format_request.rb +17 -0
  23. data/lib/linkedin/faraday_middleware/user_agent_request.rb +10 -0
  24. data/lib/linkedin/fields.rb +58 -0
  25. data/lib/linkedin/industries.rb +199 -0
  26. data/lib/linkedin/response.rb +19 -0
  27. data/lib/linkedin/version.rb +1 -1
  28. data/lib/linkedin2.rb +11 -19
  29. data/linkedin.gemspec +16 -7
  30. data/spec/api/companies_spec.rb +8 -9
  31. data/spec/api/groups_spec.rb +3 -4
  32. data/spec/api/jobs_spec.rb +2 -3
  33. data/spec/api/network_updates_spec.rb +9 -15
  34. data/spec/api/{profiles_spec.rb → people_spec.rb} +11 -13
  35. data/spec/faraday_middleware/{linkedin_error_response_spec.rb → error_response_spec.rb} +8 -21
  36. data/spec/fixtures/requests/companies.yml +25 -15
  37. data/spec/fixtures/requests/invalid.yml +7 -7
  38. data/spec/fixtures/requests/network_updates.yml +12 -12
  39. data/spec/fixtures/requests/people.yml +380 -0
  40. data/spec/spec_helper.rb +18 -19
  41. data/spec/support/coverage.rb +14 -0
  42. data/spec/support/vcr.rb +9 -0
  43. data/spec/test_app.yml +3 -2
  44. metadata +119 -38
  45. data/lib/linkedin/api/industries.rb +0 -171
  46. data/lib/linkedin/api/permissions.rb +0 -42
  47. data/lib/linkedin/api/profiles.rb +0 -71
  48. data/lib/linkedin/error.rb +0 -29
  49. data/lib/linkedin/faraday_middleware/linkedin_format_request.rb +0 -26
  50. data/lib/linkedin/industry.rb +0 -33
  51. data/spec/fixtures/requests/profiles.yml +0 -201
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ac298d199dd70fa33629431f471bd38f7247ce8c
4
- data.tar.gz: 0487998919736006a7dcc828a1d847a29cf138f4
3
+ metadata.gz: 97c526507b2313e93d3e8f8774e21ddac5b9a64b
4
+ data.tar.gz: 59b303455324238c1d9328d7e20cb165ea982218
5
5
  SHA512:
6
- metadata.gz: 7db8a53f200a4d425ac735d6f92fc2521b3caaf6d6fb878069abd874d7114b719797e1b63bbc5f15240fdce1a1035ae1179d3c30199af46e02dcb8acfe3b9fa5
7
- data.tar.gz: 9472503155331275a6ae09f763a887b9985f65c48950c38f02b5beb0375625a9c37f66e5a4c2a89854694941b02c7841c5de4e95f4569d09d407c39c6c50a43f
6
+ metadata.gz: b48333d3d41d0ecb1ac40cb11853ea92378822cb30e7c1906eba0841851d6f7b11ee630853d472ec91193284b2d703fb6741696f8c4bb5fcb31e3c8667f2a69e
7
+ data.tar.gz: 8d3f033c03d7232f64825b7a0b3bb8577e33e4d2455506298c9ae07de9b9389a2ad6dca4f10f5bf43c2236f1f7c906635ad2684f87fe78d6a3a4ca36f535e319
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ service_name: travis-pro
2
+ repo_token: eqey1MAhYCCz33208mWozM0Z3DwMxvGCR
data/.gitignore CHANGED
@@ -16,3 +16,5 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  linkedin.yml
19
+ .DS_Store
20
+ .ruby-version
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
+ --format documentation
1
2
  --color
2
- --format=nested
3
- --backtrace
3
+ --require spec_helper
data/.travis.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "1.9.3"
4
3
  - "2.0.0"
4
+ - "2.1.2"
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # LinkedIn 2
2
- [![Build Status](https://travis-ci.org/bobbrez/linkedin2.png?branch=master)](https://travis-ci.org/bobbrez/linkedin2)
3
- [![Code Climate](https://codeclimate.com/github/bobbrez/linkedin2.png)](https://codeclimate.com/github/bobbrez/linkedin2)
4
-
2
+ [![Build Status](https://travis-ci.org/bobbrez/linkedin2.svg?branch=master)](https://travis-ci.org/bobbrez/linkedin2)
3
+ [![Coverage Status](https://img.shields.io/coveralls/bobbrez/linkedin2.svg)](https://coveralls.io/r/bobbrez/linkedin2)
4
+ [![Code Climate](https://codeclimate.com/github/bobbrez/linkedin2/badges/gpa.svg)](https://codeclimate.com/github/bobbrez/linkedin2)
5
5
  A modernized LinkedIn Ruby client.
6
6
 
7
7
  ## Installation
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'rspec/core/rake_task'
3
3
 
4
4
  desc 'Open an irb session preloaded with this library'
5
5
  task :console do
6
- sh 'irb -rubygems -I lib -r linkedin2.rb'
6
+ sh 'irb -rubygems -rpry -I lib -r linkedin2.rb'
7
7
  end
8
8
 
9
9
  RSpec::Core::RakeTask.new
@@ -0,0 +1,16 @@
1
+ module LinkedIn
2
+ module API
3
+ Dir[ File.expand_path('../api/**/*.rb', __FILE__) ].each { |f| require f }
4
+
5
+ def self.included(base)
6
+ base.send :include, LinkedIn::API::Authentication,
7
+ LinkedIn::API::Companies,
8
+ LinkedIn::API::Groups,
9
+ LinkedIn::API::Invitation,
10
+ LinkedIn::API::Jobs,
11
+ LinkedIn::API::Messaging,
12
+ LinkedIn::API::NetworkUpdates,
13
+ LinkedIn::API::People
14
+ end
15
+ end
16
+ end
@@ -1,21 +1,19 @@
1
1
  module LinkedIn
2
2
  module API
3
3
  module Authentication
4
- attr_reader :state
5
-
6
- def authorize_url(params = {})
7
- params.reverse_merge! config.to_h.slice :scope, :state, :redirect_uri
4
+ def authorize_url(**params)
5
+ params.reverse_merge! configuration.to_h.slice :scope, :state, :redirect_uri
8
6
  params[:scope] = serialize_scope params[:scope]
9
- auth_code.authorize_url params
7
+ credentials.auth_code.authorize_url params
10
8
  end
11
9
 
12
10
  def request_access_token(authorization_code, params = {})
13
11
  raise Error::CSRF.new state, params[:state] if params[:state] && params[:state] != state
14
12
 
15
- params.reverse_merge! redirect_uri: config.redirect_uri
13
+ params.reverse_merge! redirect_uri: configuration.redirect_uri
16
14
  opts = { mode: :query, param_name: 'oauth2_access_token' }
17
15
 
18
- self.access_token = auth_code.get_token authorization_code, params, opts
16
+ credentials.auth_code.get_token authorization_code, params, opts
19
17
  end
20
18
 
21
19
  private
@@ -1,19 +1,25 @@
1
1
  module LinkedIn
2
2
  module API
3
3
  module Companies
4
- def company(options={})
5
- selector = if options[:selector].respond_to? :each
6
- "::(#{options[:selector].join(',')})"
7
- else
8
- "/#{options[:selector]}"
9
- end
4
+ def company(*selector, filter: nil, **opts)
5
+ root = 'companies'
6
+
7
+ selector.compact!
8
+ selector = selector.first if selector.size == 1
10
9
 
11
- fields = options[:fields]
12
- fields_string = fields.blank? ? '' : ":(#{Permissions.render_permissions fields})"
10
+ unless filter.blank?
11
+ filter = Hash[ *filter.to_s.split('=') ] unless filter.respond_to? :keys
12
+ opts[:params] = {} if opts[:params].blank?
13
+ opts[:params].to_h.merge! filter
14
+ selector = nil
15
+ end
13
16
 
14
- filter = "?#{options[:filter]}" if options[:filter].present?
17
+ if selector.respond_to? :each
18
+ selector = "companies::(#{ selector.map(&:to_param).join(',') })"
19
+ root = nil
20
+ end
15
21
 
16
- get "v1/companies#{selector}#{fields_string}#{filter}"
22
+ execute root, opts.merge(selector: selector)
17
23
  end
18
24
  end
19
25
  end
@@ -0,0 +1,39 @@
1
+ module LinkedIn
2
+ module API
3
+ module Invitation
4
+ def connect_with(recipient_selector, subject, message, type: :friend, x_auth_token: nil)
5
+ if x_auth_token.blank?
6
+ target_profile = profile recipient_selector, Fields::PROFILE_API_STD_PROFILE_REQ
7
+ x_auth_token = target_profile.body.apiStandardProfileRequest_.headers_.values_.first.value
8
+ end
9
+
10
+ x_auth_name, x_auth_value = *x_auth_token.split(':')
11
+
12
+ connection_body = build_connection_body selector: recipient_selector, subject: subject,
13
+ message: message, type: type,
14
+ auth_name: x_auth_name, auth_value: x_auth_value
15
+
16
+ execute 'people/~/mailbox', method: :post, body: connection_body
17
+ end
18
+
19
+ private
20
+
21
+ def build_connection_body(**args)
22
+ [:selector, :subject, :message, :type, :auth_name, :auth_value].each do |key|
23
+ raise ArgumentError.new "Missing / blank argument `#{key}`" if args[key].blank?
24
+ end
25
+
26
+ { recipients: { values: [ { person: { _path: "/people/#{args[:selector].to_param}" } } ] },
27
+ subject: args[:subject],
28
+ body: args[:message],
29
+ 'item-content' => {
30
+ 'invitation-request' => {
31
+ 'connect-type' => args[:type],
32
+ authorization: { name: args[:auth_name], value: args[:auth_value] }
33
+ }
34
+ }
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ module LinkedIn
2
+ module API
3
+ module Messaging
4
+ def message(subject, message, recipient_selectors, **opts)
5
+ message_body = build_message_body selectors: [recipient_selectors].flatten,
6
+ subject: subject, message: message
7
+
8
+ execute 'people/~/mailbox', opts.merge(method: :post, body: message_body)
9
+ end
10
+
11
+ private
12
+
13
+ def build_message_body(**args)
14
+ [:selectors, :subject, :message].each do |key|
15
+ raise ArgumentError.new "Missing / blank argument `#{key}`" if args[key].blank?
16
+ end
17
+
18
+ recipients = args[:selectors].map { |sel| { person: { _path: "/people/#{sel.to_param}" } } }
19
+
20
+ { recipients: { values: recipients }, subject: args[:subject], body: args[:message] }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,16 +1,12 @@
1
1
  module LinkedIn
2
2
  module API
3
3
  module NetworkUpdates
4
- def network_updates(options = {})
5
- get ['v1/people/~/network/updates', options[:selector], options[:attached_object_type]].compact.join '/'
6
- end
7
-
8
- def network_update_comments(options = {})
9
- network_updates(options.merge attached_object_type: 'update-comments')
10
- end
4
+ def network_updates(selector = '~', key: nil, type: nil, **opts)
5
+ path = ['network','updates']
6
+ path << { key: key }.to_param if key
7
+ path << type
11
8
 
12
- def network_update_likes(options = {})
13
- network_updates(options.merge attached_object_type: 'likes')
9
+ execute 'people', opts.merge(selector: selector, path: path.compact.join('/'))
14
10
  end
15
11
  end
16
12
  end
@@ -0,0 +1,18 @@
1
+ module LinkedIn
2
+ module API
3
+ module People
4
+ def profile(selector = '~', **opts)
5
+ execute 'people', opts.merge(selector: selector)
6
+ end
7
+
8
+ def connections(selector = '~', **opts)
9
+ execute 'people', opts.merge(selector: selector, path: 'connections')
10
+ end
11
+
12
+ def people_search(query, **opts)
13
+ wrapped_fields = opts[:fields].blank? ? nil : { people: opts[:fields] }
14
+ execute 'people-search', opts.merge(params: query, fields: wrapped_fields)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,96 +1,58 @@
1
1
  module LinkedIn
2
2
  class Client
3
- extend Forwardable
4
-
5
3
  include Configuration
6
- include LinkedIn::API::Authentication
7
- include LinkedIn::API::Profiles
8
- include LinkedIn::API::NetworkUpdates
9
- include LinkedIn::API::Companies
10
-
11
- HTTP_METHODS = [:get, :post, :put, :patch, :delete, :headers].freeze
12
-
13
- attr_writer :profile_fields
14
- attr_reader :access_token
4
+ include LinkedIn::API
15
5
 
16
- def_delegators :@access_token, :expires?, :expired?, :request
17
-
18
- def initialize(options={}, &block)
19
- configure options, &block
20
- self.access_token ||= self.config.access_token.to_s
6
+ def initialize(**config, &block)
7
+ configure config, &block
21
8
  end
22
9
 
23
- def auth_code
24
- connection.auth_code
10
+ def credentials
11
+ @credentials ||= Credentials.new configuration
25
12
  end
26
13
 
27
14
  def connection
28
- @connection ||= OAuth2::Client.new config.key, config.secret, oauth2_options do |faraday|
29
- faraday.request :url_encoded
30
- faraday.request :json
31
- faraday.request :linkedin_format, config.request_format
32
-
33
- faraday.response :mashify
34
- faraday.response :linkedin_errors
35
- faraday.response :logger, config.logger
36
- faraday.response :json, content_type: /\bjson$/
37
-
38
- faraday.adapter :net_http
15
+ @connection ||= Faraday.new 'https://api.linkedin.com' do |conn|
16
+ conn.request :json
17
+ conn.request :url_encoded
18
+ conn.request :linkedin_credentials, configuration
19
+ conn.request :linkedin_format
20
+ conn.request :linkedin_user_agent
21
+
22
+ conn.response :linkedin_errors
23
+ conn.response :mashify
24
+ conn.response :logger, configuration.logger
25
+ conn.response :json, content_type: /\bjson$/
26
+
27
+ conn.adapter Faraday.default_adapter
39
28
  end
40
29
  end
41
30
 
42
- def access_token=(token)
43
- if token.kind_of? String
44
- options = { access_token: token, mode: :query, param_name: 'oauth2_access_token' }
45
- return @access_token = OAuth2::AccessToken.from_hash(connection, options)
46
- end
47
-
48
- @access_token = token
31
+ def headers
32
+ @headers ||= {}
49
33
  end
50
34
 
51
- def profile_fields
52
- return @profile_fields if @profile_fields
53
- scopes = config.scope unless config.scope.respond_to?(:values)
54
- scopes ||= config.scope
55
-
56
- @profile_fields = scopes.reduce([]) { |fields, scope| fields + LinkedIn.send(scope) }
57
- end
58
-
59
- def method_missing(method, *args, &body)
60
- return simple_request(method, args[0], (args[1] || {}), &body) if HTTP_METHODS.include? method
61
- super
62
- end
63
-
64
- def self.default_config
65
- {
66
- request_format: :json,
67
-
68
- key: nil,
69
- secret: nil,
70
- access_token: nil,
71
-
72
- scope: ['r_basicprofile'],
73
- state: Utils.generate_random_state,
74
- redirect_uri: 'http://localhost',
75
-
76
- logger: Logger.new('/dev/null')
77
- }
35
+ def params
36
+ @params ||= {}
78
37
  end
79
38
 
80
39
  private
81
40
 
82
- def simple_request(method, path, options={}, &body)
83
- request(method, path, options, &body).body
41
+ def override(global, overrides)
42
+ global.to_h.merge overrides.to_h
84
43
  end
85
44
 
86
- def auth_code
87
- connection.auth_code unless connection.nil?
88
- end
45
+ def execute(root, method: :get, selector: nil, fields: nil, **opts)
46
+ rendered_fields = Fields.render fields
47
+ query = ['v1', root, selector.to_param, opts[:path]].compact.join('/').concat(rendered_fields)
48
+
49
+ response = connection.send method, query do |req|
50
+ req.headers.update override(@headers, opts[:headers])
51
+ req.params.update override(@params, opts[:params])
52
+ req.body = opts[:body].to_json if opts[:body]
53
+ end
89
54
 
90
- def oauth2_options
91
- { site: 'https://api.linkedin.com',
92
- authorize_url: 'https://www.linkedin.com/uas/oauth2/authorization',
93
- token_url: 'https://www.linkedin.com/uas/oauth2/accessToken' }
55
+ Response.new response
94
56
  end
95
57
  end
96
58
  end
@@ -1,41 +1,56 @@
1
1
  module LinkedIn
2
2
  module Configuration
3
3
  module ClassConfiguration
4
- def config
5
- @config ||= reset
4
+ def default_config
5
+ {
6
+ request_format: :json,
7
+
8
+ app_key: nil,
9
+ app_secret: nil,
10
+ access_token: nil,
11
+
12
+ scope: ['r_basicprofile'],
13
+ redirect_uri: 'http://localhost',
14
+
15
+ logger: Logger.new('/dev/null')
16
+ }
17
+ end
18
+
19
+ def configuration
20
+ @configuration ||= OpenStruct.new default_config
6
21
  end
7
22
 
8
23
  def reset
9
- @config = OpenStruct.new default_config
24
+ configuration.marshal_load default_config
10
25
  end
11
26
  end
12
27
 
13
28
  module InstanceConfiguration
14
- def config
15
- @config ||= reset
29
+ def configuration
30
+ @configuration ||= self.class.configuration.dup
16
31
  end
17
32
 
18
33
  def reset
19
- @config = self.class.config.dup
34
+ @configuration.marshal_load self.class.configuration
20
35
  end
21
36
  end
22
37
 
23
38
  module BaseConfiguration
24
39
  def configure(config={}, &block)
25
- self.config.marshal_load self.config.marshal_dump.merge(config)
40
+ self.configuration.marshal_load self.configuration.marshal_dump.merge(config)
26
41
 
27
- yield self.config if block_given?
42
+ yield self.configuration if block_given?
28
43
 
29
- self.config
44
+ self.configuration
30
45
  end
31
46
 
32
- def load(file_path='linkedin.yml')
47
+ def load(file_path = 'linkedin.yml')
33
48
  config = YAML::load(File.open(file_path)).symbolize_keys
34
49
  configure config
35
50
  end
36
51
 
37
52
  def config(*keys)
38
- config.marshal_dump.slice(*keys)
53
+ configuration.marshal_dump.slice(*keys)
39
54
  end
40
55
  end
41
56